Java线程通信及线程虚假唤醒知识总结

线程通信

线程在内部运行时,线程调度具有一定的透明性,程序通常无法控制线程的轮换执行。但Java本身提供了一些机制来保证线程协调运行。

假设目前系统中有两个线程,分别代表存款和取钱。当钱存进去,立马就取出来挪入指定账户。这涉及到线程间的协作,使用到Object类提供的wait()、notify()、notifyAll()三个方法,其不属于Thread类,而属于Object,而这三个方法必须由监视器对象来调用:

  • synchronized修饰的方法,因为该类的默认实例(this)就是同步监视器,因此可以在同步方法中直接调用
  • synchronized修饰的同步代码块,同步监视器是synchronized括号里的对象,因此必须使用该对象来调用

三个方法解释如下:

  • wait():当前线程等待,释放当前对象锁,让出CPU,直到其他线程使用notify或者notifyAll唤醒该线程
  • notify():唤醒在此同步监视器上等待的单个线程,若存在多个线程,则随机唤醒一个。执行了notify不会马上释放锁,只有完全退出synchronized代码块或者中途遇到wait,呈wait状态的线程才可以去争取该对象锁
  • notifyAll():唤醒在此同步监视器上的所有线程,同上。

现在用两个同步方法分别代表存钱取钱

  • 当余额为0时,进入存钱流程,执行存钱操作后,唤醒取钱线程
  • 当余额为0时,进入取钱流程,发现num==0,进入阻塞状态,等待被唤醒
/**
     * 存一块钱
     *
     * @throws InterruptedException
     */
    public synchronized void increase() throws InterruptedException {
        // 当余额为1,说明已经存过钱,等待取钱。存钱方法阻塞
        if (num == 1) {
            this.wait();
        }
        // 执行存钱操作
        num++;
        System.out.println(Thread.currentThread().getName() + ":num=" + num);
        // 唤醒其他线程
        this.notifyAll();
    }

    /**
     * 取一块钱
     *
     * @throws InterruptedException
     */
    public synchronized void decrease() throws InterruptedException {
        // 当余额为0,说明已经取过钱,等待存钱。取钱方法阻塞
        if (num == 0) {
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + ":num=" + num);
        this.notifyAll();
    }

调用方法:

private int num = 0;

    public static void main(String[] args) {
        Test test = new Test();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    test.increase();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "存钱").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    test.decrease();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "取钱").start();
    }

结果没有什么问题

线程虚假唤醒

上述线程通信看起来似乎没有什么问题,但若此时将存钱和取钱的人数各增加1,再看运行结果

private int num = 0;

    public static void main(String[] args) {
        Test test = new Test();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    test.increase();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "存钱1").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    test.decrease();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "取钱1").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    test.increase();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "存钱2").start();

        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    test.decrease();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "取钱2").start();
    }

产生的结果已经不是最初的只有0和1

造成这个结果的原因就是线程间的虚假唤醒

由于目前分别有多个取款和存款线程。假设其中一个存款线程执行完毕,并使用wait释放同步监视器锁定,那其余多个取款线程将同时被唤醒,此时余额为1,如果有10个同时取钱,那余额会变为-9,造成结果错误。

因此,每次线程从wait中被唤醒,都必须再次测试是否符合唤醒条件,如果不符合那就继续等待。

由于多个线程被同时唤醒,在if(xxxx){wait();}处 if判断只会执行一次,当下一个被唤醒的线程过来时,由于if已经判断过,则直接从wait后面的语句继续执行,因此将if换成while可解决该问题,下次被唤醒的线程过来,while重新判断一下,发现上一个被唤醒的线程已经拿到锁,因此这个被虚假唤醒的线程将继续等待锁。

 /**
     * 存一块钱
     *
     * @throws InterruptedException
     */
    public synchronized void increase() throws InterruptedException {
        while (num == 1) {// 防止每次进来的唤醒线程只判断一次造成虚假唤醒,替换成while
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + ":num=" + num);
        this.notifyAll();
    }

    /**
     * 取一块钱
     *
     * @throws InterruptedException
     */
    public synchronized void decrease() throws InterruptedException {
        while (num == 0) {
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + ":num=" + num);
        this.notifyAll();
    }

再次运行,结果正常:

到此这篇关于Java线程通信及线程虚假唤醒知识总结的文章就介绍到这了,更多相关Java线程通信及线程虚假唤醒内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java经典面试题汇总--多线程

    目录 1. 并行和并发有什么区别? 2. 线程和进程的区别? 3. 守护线程是什么? 4. 实现多线程的方式有哪些? 5. 说一下 runnable 和 callable 有什么区别? 6. sleep() 和 wait() 有什么区别? 7. 线程有哪些状态? 8. notify()和 notifyAll()有什么区别? 9. 线程的 run() 和 start() 有什么区别? 10. 创建线程池有哪几种方式? 11. 线程池中 submit() 和 execute() 方法有什么区别? 1

  • Java实现多线程聊天室

    本文实例为大家分享了Java实现多线程聊天室的具体代码,供大家参考,具体内容如下 用多线程来实现,功能会比单线程聊天室更加齐全,也更人性化一点. 多线程版本的聊天室 1. 功能分析: 实现用户注册,上线,下线 实现群聊和私聊 统计当前在线人数 2. 服务端实现 1.维护所有的在线用户 2.注册功能:客户端名称,添加到服务器的客户端集合里 3.群聊功能:客户端发送消息,所有的客户端都能接收到 4.私聊功能:客户端与指定客户端进发送和接收消息 5.退出功能: 从服务器客户端集合中移除客户端 3. 客

  • 详解在Java中如何创建多线程程序

    创建多线程程序的第一种方式:创建Thread类的子类 java.lang.Thread类:是描述线程的类,我们想要实现多线程程序,就必须继承Thread类 实现步骤: 1.创建一个Thread类的子类 2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么?) 3.创建Thread类的子类对象 4.调用Thread类中的方法start方法,开启新的线程,执行run方法 void start()使该线程开始执行;Java虚拟机调用该线程的run方法. 结果是两

  • Java并发编程之线程间的通信

    一.概念简介 1.线程通信 在操作系统中,线程是个独立的个体,但是在线程执行过程中,如果处理同一个业务逻辑,可能会产生资源争抢,导致并发问题,通常使用互斥锁来控制该逻辑.但是在还有这样一类场景,任务执行是有顺序控制的,例如常见的报表数据生成: 启动数据分析任务,生成报表数据: 报表数据存入指定位置数据容器: 通知数据搬运任务,把数据写入报表库: 该场景在相对复杂的系统中非常常见,如果基于多线程来描述该过程,则需要线程之间通信协作,才能有条不紊的处理该场景业务. 2.等待通知机制 如上的业务场景,

  • 详解Java线程的创建及休眠

    一.进程vs线程 1.进程是系统分配资源的最小单位:线程是系统调度的最小单位 2.一个进程中至少要包含一个线程 3.线程必须要依附于继承,线程是进程实质工作的一个最小单位 二.线程的创建方式 继承Thread类 实现线程的创建(2种写法) 1种写法 public class ThreadDemo03 { static class MyThread extends Thread{ @Override public void run(){ System.out.println("线程名称:"

  • Java线程通信及线程虚假唤醒知识总结

    线程通信 线程在内部运行时,线程调度具有一定的透明性,程序通常无法控制线程的轮换执行.但Java本身提供了一些机制来保证线程协调运行. 假设目前系统中有两个线程,分别代表存款和取钱.当钱存进去,立马就取出来挪入指定账户.这涉及到线程间的协作,使用到Object类提供的wait().notify().notifyAll()三个方法,其不属于Thread类,而属于Object,而这三个方法必须由监视器对象来调用: synchronized修饰的方法,因为该类的默认实例(this)就是同步监视器,因此

  • C#通过接口与线程通信(捕获线程状态)示例代码

    提示:本文所提到的线程状态变化,并不是指线程启动.暂停.停止,而是说线程内部状态的迁移.随着软件技术不断发展,用户需求不断提升,多线程的重要性日益凸显. 关于线程,通俗的讲,每当启动一个exe应用程序,都会创建一个进程和一个主线程,主线程用来处理界面绘制.界面事件响应等工作,而进程则是线程的容器,主线程和用户创建的新线程,都将在相应的进程中维护.由此可知,一个程序之所以能工作,是线程的功劳,进程仅仅是容器而已,一旦程序的主线程被阻塞,就会造成界面无响应等现象,这时候就要用多线程解决问题.然而,在

  • java 多线程-线程通信实例讲解

    线程通信的目标是使线程间能够互相发送信号.另一方面,线程通信使线程能够等待其他线程的信号. 通过共享对象通信 忙等待 wait(),notify()和 notifyAll() 丢失的信号 假唤醒 多线程等待相同信号 不要对常量字符串或全局对象调用 wait() 通过共享对象通信 线程间发送信号的一个简单方式是在共享对象的变量里设置信号值.线程 A 在一个同步块里设置 boolean 型成员变量 hasDataToProcess 为 true,线程 B 也在同步块里读取 hasDataToProc

  • Java深入探索线程安全和线程通信的特性

    目录 一.线程安全(重点) 1.线程安全概念 2.产生线程不安全的情况 3.线程不安全的原因 4.如何解决线程不安全问题 二.synchronized关键字 1.使用 2.特性 三.volatile关键字 1.保证可见性 2.禁止指令重排序 3.不保证原子性 四.wait和notify(线程间的通信) 1.wait()方法 2.notify()和notifyAll()方法 3.wait和sleep的对比 五.线程和进程的比较 1.线程的优点 2.线程和进程的区别 一.线程安全(重点) 1.线程安

  • Java实现线程通信的案例讲解

    什么是线程通信.如何实现? 所谓线程通信就是线程间相互发送数据,线程通信通常通过共享一个数据的方式实现. 线程间会根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做. 线程通信常见模型 生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费数据. 要求:生产者线程生产完数据后,唤醒消费者,然后等待自己;消费者消费完该数据后,唤醒生产者,然后等待自己 public class 多线程_5线程通信 extends Thread{ public static void main(Str

  • JAVA 线程通信相关知识汇总

    两个线程之间的通信 多线程环境下CPU会随机的在线程之间进行切换,如果想让两个线程有规律的去执行,那就需要两个线程之间进行通信,在Object类中的两个方法wait和notify可以实现通信. wait方法可以使当前线程进入到等待状态,在没有被唤醒的情况下,线程会一直保持等待状态. notify方法可以随机唤醒单个在等待状态下的线程. 来实现这样的一个功能: 让两个线程交替在控制台输出一行文字 定义一个Print类,有两个方法print1和print2,分别打印一行不同的内容 package c

  • Java多线程之线程通信生产者消费者模式及等待唤醒机制代码详解

    前言 前面的例子都是多个线程在做相同的操作,比如4个线程都对共享数据做tickets–操作.大多情况下,程序中需要不同的线程做不同的事,比如一个线程对共享变量做tickets++操作,另一个线程对共享变量做tickets–操作,这就是大名鼎鼎的生产者和消费者模式. 正文 一,生产者-消费者模式也是多线程 生产者和消费者模式也是多线程的范例.所以其编程需要遵循多线程的规矩. 首先,既然是多线程,就必然要使用同步.上回说到,synchronized关键字在修饰函数的时候,使用的是"this"

  • Java等待唤醒机制线程通信原理解析

    这篇文章主要介绍了Java等待唤醒机制线程通信原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 线程间通信 概念:多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同.比如:线程A用来生成包子的,线程B用来吃包子的,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产,一个是消费,那么线程A与线程B之间就存在线程通信问题. 为什么要处理线程间通信: 多个线程并发执行时, 在默认情况下CPU是随机切换线程的,当我们需要多个

  • java多线程编程学习(线程间通信)

    一.概要 线程是操作系统中独立的个体,但这些个体如果不经过特殊的处理就不能成为一个整体,线程间的通信就是成为整体的必用方案之一.可以说,使线程进行通信后,系统之间的交互性会更强大,在大大提高cpu利用率的同时还会使程序员对各线程任务在处理过程中进行有效的把控和监督. 二.等待/通知机制 1."wait/notify"机制:等待/通知机制,wait使线程暂停运行,而notify 使暂停的线程继续运行.用一个厨师和服务员的交互来说明: (1) 服务员取到菜的时间取决于厨师,所以服务员就有&

  • Java线程通信详解

    线程通信用来保证线程协调运行,一般在做线程同步的时候才需要考虑线程通信的问题. 1.传统的线程通信 通常利用Objeclt类提供的三个方法: wait() 导致当前线程等待,并释放该同步监视器的锁定,直到其它线程调用该同步监视器的notify()或者notifyAll()方法唤醒线程. notify(),唤醒在此同步监视器上等待的线程,如果有多个会任意选择一个唤醒 notifyAll() 唤醒在此同步监视器上等待的所有线程,这些线程通过调度竞争资源后,某个线程获取此同步监视器的锁,然后得以运行.

随机推荐