java线程浅析[多线程同步]

时间:2021-05-21 18:00:02

java线程浅析[多线程同步]

1:什么是多线程同步?
2:怎么解决多线程同步的问题?
3:synchronized同步锁机制
4:java中自带的一个同步锁ReentrantLock
5:通过java中的Semaphore实现线程的同步


什么是多线程同步?

首先举一个生活中最简单的例子,公用厕所中的一个大号坑,假设每一个想上厕所的人都是一个线程,本来按照正常的理解来说应该是一个接着一个,后面一个等前一个使用完毕出来之后才有使用权,但是万一出现这样一种情况,就是突然两个人同时抢到了一个坑 ,那么谁先用??而java的多线程同步相对来说也近似这样的一种场景,为了解决对共享资源同时并发的这样一种情况来定的

 多线程同步就是为了多个线程访问同一个数据对象,对数据造成破坏或者对程序结构引起很大的影响。它保证了多线程能够安全访问竞争资源的一个手段。

上述的例子中就是:厕所对所有人来说是共享的竞争资源。那么怎么有效的能够按照顺序来进行,就必须要给厕所安个门,安把锁,这样只要一个人抢占了资源后,那么就不会再出现另外一个也进入使用的场景,保证了其能够按照顺序对共享资源进行依次的访问。

下面上一个错误的代码:看看大概有多少个人同时去抢占厕所

package com.demo.thread;

/**
* 多人上厕所的问题,厕所只有一个坑,如果这样算,那现在如果有100个人想去上厕所,其必须是要一个一个进去,
* 也就是这里的共享资源是厕所,而这100个人都可以去看成线程的操作
*
* 主要是使用synchronized进行加锁同步
* @author Administrator
*/

public class ThreadTestSync {

public static void main(String[] args) {
//当多个线程对同一个共享资源进行竞争的时候,这个时候就是比较容易出现
Toilet toilet= new Toilet();
for(int i = 0 ;i<100 ; i++){
new Thread(new Person(toilet)).start();
}
}
}

class Toilet {
void inToilet(){
System.out.println("开厕所门");
}

void outToilet(){
System.out.println("关厕所门");
}
}

class Person extends Thread{

Toilet toilet;
public Person(Toilet toilet){
this.toilet = toilet;
}

@Override
public void run() {
// TODO Auto-generated method stub
toilet.inToilet();
try {
sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
toilet.outToilet();
}

}

下面是输出的结果的部分截图:从图中可以看出,这100个人很多时候都是去厕所,但是只有一个坑,在肯定是不符合逻辑的。
java线程浅析[多线程同步]

所以线程同步为的是解决什么问题??为的就是解决同一时间对同一资源访问的时候,容易出现的并发问题,也就是同时有多个线程对同一资源进行了访问,导致资源显示出现异常的现象

2:怎么解决多线程同步的问题?

那么在多线程出现并发的这样的一种情况下面,我们应该怎么去解决呢,目前主要就是采用加锁的形式去解决。试想一下什么是锁。门锁是用来干嘛的,一把锁一般情况下对应一个钥匙,按照上面的逻辑,我只要给厕所门上一把锁,进去的人把们锁着,然后出来后把锁还给另外一个人,这样按照顺序的形式,就可以了。那么这100个人也就是依次的去开门上厕所,目前最基础的就是下面3种方法:

 1:synchronized同步锁机制
2:ReentrantLock锁,
3:Semaphore实现线程信号量对线程锁的控制

3:synchronized同步锁机制

synchronized在英文中的意思本来就是有同步的意思。所以java中也是采用synchronized关键字来进行修饰的,
实现线程同步的关系,注意无论synchronized采用什么样的加锁方式,只要当加锁的代码域执行完毕之后。锁是有系统自动进行释放的

同步的方式:

1. synchronized去修饰方法:

直接去修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象,
注意:也就是整方法执行完毕的时候,锁才会去释放,切记,不要一个外部方法中去同时调用两个加锁的方法,
因为这个时候会导致,如果前面一个锁被释放掉之后,后面的方法如果没上锁
这就同样会引起其他线程会获取锁资源,而导致同步失败
如:public synchronized void inToilet(){}

2. synchronized去修饰代码块:

    //修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象(用的相对是比较多的),
如:
synchronized (Person.class) {
toilet.inToilet();
try {
sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
toilet.outToilet();
}

3 synchronized去修饰静态方法:

     syncchronized同步静态方法块的时候,这个锁是针对所有对象的,属于类的,不管是任何对象,其在做counter增加的时候都是会加锁操作的,
这个时候对于静态变量的加锁操作同步的,同一时间,只允许一个person进行递增的操作
如:
public synchronized static void personCount(){
person_count++;
System.out.println("person_count:"+person_count);
}

4. synchronized去修饰方法中的具体的执行:

 修饰方法中具体的实现,同样是针对一个对象来说的
void function(){
synchronized (this) {
inToilet();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outToilet();
}
}

以下是完善后的代码,有兴趣的可以看一下

package com.demo.thread;

/**
* 多人上厕所的问题,厕所只有一个坑,如果这样算,那现在如果有100个人想去上厕所,其必须是要一个一个进去,
* 也就是这里的共享资源是厕所,而这100个人都可以去看成线程的操作
*
* 主要是使用synchronized进行加锁同步
* @author Administrator
*/

public class ThreadTestSync {

public static void main(String[] args) {
//当多个线程对同一个共享资源进行竞争的时候,这个时候就是比较容易出现
Toilet toilet= new Toilet();
for(int i = 0 ;i<100 ; i++){
new Thread(new Person(toilet)).start();
}
}

}

class Toilet {
void inToilet(){
System.out.println("开厕所门");
}

void outToilet(){
System.out.println("关厕所门");
}

/*
* 直接去修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象,
* 注意:也就是整方法执行完毕的时候,锁才会去释放,切记,不要一个外部方法中去同时调用两个加锁的方法,因为这个时候会导致,如果前面一个锁被释放掉之后,后面的方法如果没上锁
* 这就同样会引起其他线程会获取锁资源,而导致同步失败
*
* 如:synchronized void inToilet(){}
* synchronized void outToilet();
* void function(){inToilet(),outToilet()}; //锁被加给了子方法,这样就会导致
*
*/


// synchronized void function(){
// inToilet();
// outToilet();
// }

/**
* 修饰方法中具体的实现,同样是针对一个对象来说的
*/

void function(){
synchronized (this) {
inToilet();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
outToilet();
}
}
}


class Person extends Thread{
Toilet toilet;

static int person_count = 0;

public Person(Toilet toilet){
this.toilet = toilet;
}

/**
* syncchronized同步静态方法块的时候,这个锁是针对所有对象的,属于类的,不管是任何对象,其在做counter增加的时候都是会加锁操作的,这个时候
* 对于静态变量的加锁操作同步的,同一时间,只允许一个person进行递增的操作
*/

public synchronized static void personCount(){
person_count++;
System.out.println("person_count:"+person_count);
}

@Override
public void run() {
// TODO Auto-generated method stub
//修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象(用的相对是比较多的)
// synchronized (Person.class) {
// toilet.inToilet();
personCount();
try {
sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// toilet.function();

// toilet.outToilet();
// }
}
}

代码中混合注释掉的部分可能有点多,可以根据上面的分类来进行区分,代码上面的代码也是可以直接进行run的,因为加锁的方式比较多,生成的结果也都是一样的,所以在这里就不再去执行代码了

java中自带的一个同步锁ReentrantLock

ReentrantLock是jdk在高一点版本以后,在原来锁机制的基础之上封装而来的。也是为了解决同步锁机制,而生的,
它的使用比synchronized的使用更加简便。而且其内部也封装了很多查验,校验类的方法
package com.demo.thread;

import java.util.concurrent.locks.ReentrantLock;

/**
* 多人上厕所的问题,厕所只有一个坑,如果这样算,那现在如果有100个人想去上厕所,其必须是要一个一个进去,
* 也就是这里的共享资源是厕所,而这100个人都可以去看成线程的操作
*
* 主要是使用synchronized进行加锁同步
* @author Administrator
*/

public class ThreadTetsSyncTwo {

public static void main(String[] args) {
//当多个线程对同一个共享资源进行竞争的时候,这个时候就是比较容易出现
ToiletTwo toilet= new ToiletTwo();
ReentrantLock lock = new ReentrantLock();
for(int i = 0 ;i<100 ; i++){
new Thread(new Human(toilet,lock)).start();
}

}

}

class ToiletTwo{
void inToilet(){
System.out.println("开厕所门");
}

void outToilet(){
System.out.println("关厕所门");
}

}


class Human extends Thread{
ToiletTwo toilet;
ReentrantLock lock;

public Human(ToiletTwo toilet,ReentrantLock lock){
this.toilet = toilet;
this.lock = lock;
}

@Override
public void run() {
// TODO Auto-generated method stub
//对执行的代码块进行上锁机制
lock.lock();
toilet.inToilet();
try {
sleep(5);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
toilet.outToilet();
//执行完毕之后就释放锁
lock.unlock();
}

}

在上面的方法基础之上,稍微做了一些修改。一定要注意,所有争夺共享资源的线程一定是要同一把锁,如果是多把锁的话,那就并不适用,所有在这里我的锁是从外部传入进去的。那样,所有的线程都只会去针对这一把锁

通过java中的Semaphore实现线程的同步

Semaphore,是负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。也是操作系统中用于控制进程同步互斥的量。这个相对来说可能用的也比较少一点吧,但是在解决线程同步的时候却也是非常的重要

Semaphore其实是一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个 acquire(),然后再获取该许可。每个 release() 添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。拿到信号量的线程可以进入代码,否则就等待。通过acquire()和release()获取和释放访问许可。

首先先看一下Semaphore的构造参数吧:

/**
* Creates a {@code Semaphore} with the given number of
* permits and nonfair fairness setting.
*
* @param permits the initial number of permits available.
* This value may be negative, in which case releases
* must occur before any acquires will be granted.
*
*/

public Semaphore(int permits) {
sync = new NonfairSync(permits);
}

/**
* Creates a {@code Semaphore} with the given number of
* permits and the given fairness setting.
*
* @param permits the initial number of permits available.
* This value may be negative, in which case releases
* must occur before any acquires will be granted.
* @param fair {@code true} if this semaphore will guarantee
* first-in first-out granting of permits under contention,
* else {@code false}
*/

public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

Semaphore的构造方法中显示出来的都是有参的构造方法:

permits - 初始的可用许可数目。此值可能为负数,在这种情况下,必须在授予任何获取前进行释放。
fair - 如果此信号量保证在争用时按先进先出的顺序授予许可,则为 ture,否则为 false。

如果在上述案例的情况下,该怎么使用Semaphore来进行线程的同步呢??
直接上代码了,记住其为permits 为1的时候也就相当于对线程进行了加锁操作

package com.demo.thread;

import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.ReentrantLock;

/**
* 多人上厕所的问题,厕所只有一个坑,如果这样算,那现在如果有100个人想去上厕所,其必须是要一个一个进去,
* 也就是这里的共享资源是厕所,而这100个人都可以去看成线程的操作
*
* 主要是使用synchronized进行加锁同步
* @author Administrator
*/

public class ThreadTetsSyncTwo {

public static void main(String[] args) {
//当多个线程对同一个共享资源进行竞争的时候,这个时候就是比较容易出现
ToiletTwo toilet= new ToiletTwo();
Semaphore lock = new Semaphore(1,false);
for(int i = 0 ;i<100 ; i++){
new Thread(new Human(toilet,lock)).start();
}

}

}

class ToiletTwo{
void inToilet(){
System.out.println("开厕所门");
}

void outToilet(){
System.out.println("关厕所门");
}

}


class Human extends Thread{
ToiletTwo toilet;
Semaphore lock;

public Human(ToiletTwo toilet,Semaphore lock){
this.toilet = toilet;
this.lock = lock;
}

@Override
public void run() {
// TODO Auto-generated method stub
//对执行的代码块进行上锁机制
try {
lock.acquire();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
System.out.print(this.getName()+":");
toilet.inToilet();
try {
sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
toilet.outToilet();
//执行完毕之后就释放锁
lock.release();
}

}

但是关于Semaphore的使用,可能很多时候都并不是用来进行加锁操作的,而是进行多线程执行数量上的控制,比如android在图片加载的时候一次性刷了100个图片。,但是我们不可能同时让其加载100个,而是选择一次只允许加载5个,那这个时候我们就开放5个许可,这样同时执行的也就只有5个线程。多用在与线程池配合使用上

如果想了解Semaphore的案例可以参考http://blog.csdn.net/shihuacai/article/details/8856526,其对Semaphore做了简单的使用

最后谢谢观看。写的不好的地方可以指出

欢迎访问博客