JUC学习(三):synchronized和Lock实现线程间通信(包含虚假唤醒的讲解)

        线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。我们来基本一道面试常见的题目来分析 :

        场景---两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求用线程间通信

 

一、synchronized实现 

/**
 * 实现线程A对一个值+1,线程B对该值-1
 */

//第一步:创建资源类,定义属性和操作方法
class Share{
    //目标值
    int number = 0;

    //+1操作
    public synchronized void incr() throws InterruptedException {
        //第二步:判断->操作->通知
        //判断
        if (number != 0){
            this.wait();
        }
        //操作
        number++;
        System.out.println(Thread.currentThread().getName()+":"+number);
        //通知其他线程
        this.notifyAll();
    }

    //-1操作
    public synchronized void decr() throws InterruptedException {
        if (number != 1){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+":"+number);
        this.notifyAll();
    }
}

public class ThreadDemo01 {

    //第二步,创建多个线程,调用资源类中的操作方法
    public static void main(String[] args) {

        Share share = new Share();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程B").start();
    }
}

 

二、虚假唤醒问题 

        现在我们将进程变为四个,A、C进程负责加,B、D进程负责减  

//第一步:创建资源类,定义属性和操作方法
class Share{
    //目标值
    int number = 0;

    //+1操作
    public synchronized void incr() throws InterruptedException {
        //第二步:判断->操作->通知
        //判断
        if (number != 0){
            this.wait();
        }
        //操作
        number++;
        System.out.println(Thread.currentThread().getName()+":"+number);
        //通知其他线程
        this.notifyAll();
    }

    //-1操作
    public synchronized void decr() throws InterruptedException {
        if (number != 1){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+":"+number);
        this.notifyAll();
    }
}

public class ThreadDemo01 {

    //第二步,创建多个线程,调用资源类中的操作方法
    public static void main(String[] args) {

        Share share = new Share();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程B").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程C").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程D").start();
    }
}

 

                ​​​​​​​        ​​​​​​​        ​​​​​​​        ​​​​​​​         

        我们可以看到,结果并不是严格的一加一减,而是出现了2、3这样的数字,这是为什么呢?

        这就是要讲的虚假唤醒的问题。

        查阅jdk1.8版本的API,可以看到wait方法中有这样一段话:

         意思是,我们代码中的wait应该放在循环中来避免虚假唤醒,不应该写成:

if (number != 0){
    this.wait();
}

        而应该写成:

while (number != 0){
    this.wait();
}

        为什么会导致这样呢?什么是虚假唤醒呢?

        简而言之,就是wait()方法在唤醒之后,会直接在之前等待的地方继续执行,而不会再执行前面的判断了,这就叫做虚假唤醒。所以要放在循环中,让他唤醒后重新去做判断,避免虚假唤醒的问题。

        所以Share类中正确的代码应是:

class Share{
    //目标值
    int number = 0;

    //+1操作
    public synchronized void incr() throws InterruptedException {
        //第二步:判断->操作->通知
        //判断
        while (number != 0){
            this.wait();
        }
        //操作
        number++;
        System.out.println(Thread.currentThread().getName()+":"+number);
        //通知其他线程
        this.notifyAll();
    }

    //-1操作
    public synchronized void decr() throws InterruptedException {
        while (number != 1){
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+":"+number);
        this.notifyAll();
    }
}

        运行结果:

线程A:1
线程B:0
线程A:1
线程B:0
线程C:1
线程D:0
线程A:1
线程D:0
线程C:1
线程B:0
线程C:1
线程D:0
线程A:1
线程D:0
线程C:1
线程B:0
线程C:1
线程D:0
线程A:1
线程D:0
线程C:1
线程B:0
线程C:1
线程D:0
线程A:1
线程D:0
线程C:1
线程B:0
线程C:1
线程D:0
线程A:1
线程D:0
线程C:1
线程B:0
线程A:1
线程B:0
线程A:1
线程B:0
线程A:1
线程B:0

Process finished with exit code 0

 

 

三、Lock实现四线程操作

/**
 * Lock实现:线程A、C将number值从0变为1,线程B、D将number值从1变为0
 */

class Share{
    private int number = 0;

    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    //+1操作
    public void incr() throws InterruptedException {

        lock.lock();

        try{
            while(number != 0){
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+":"+number);
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }

    //-1操作
    public void decr() throws InterruptedException {

        lock.lock();

        try {
            while(number != 1){
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+":"+number);
            condition.signalAll();

        } finally {
            lock.unlock();
        }
    }
}

public class ThreadDemo02 {

    public static void main(String[] args) {

        Share share = new Share();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程B").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程C").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "线程D").start();
    }
}

 

        运行结果:

线程A:1
线程D:0
线程A:1
线程B:0
线程C:1
线程D:0
线程A:1
线程B:0
线程C:1
线程D:0
线程A:1
线程B:0
线程C:1
线程D:0
线程A:1
线程B:0
线程C:1
线程D:0
线程A:1
线程B:0
线程C:1
线程D:0
线程A:1
线程B:0
线程C:1
线程D:0
线程A:1
线程B:0
线程C:1
线程D:0
线程A:1
线程B:0
线程C:1
线程D:0
线程A:1
线程B:0
线程C:1
线程D:0
线程C:1
线程B:0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值