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

人生之路不会是一帆风顺的,我们会遇上顺境,也会遇上逆境,在所有成功路上折磨你的,背后都隐藏着激励你奋发向上的动机,人生没有如果,只有后果与结果,成熟,就是用微笑来面对一切小事。

导读:本篇文章讲解 JUC学习(三):synchronized和Lock实现线程间通信(包含虚假唤醒的讲解),希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

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

        场景—两个线程,一个线程对当前数值加 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();
    }
}

 

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

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

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

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

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

         意思是,我们代码中的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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/125011.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!