JUC之线程通信

56 篇文章 5 订阅
35 篇文章 4 订阅
本文详细解析了Java中多线程的wait()、notify()方法的使用,强调了它们必须在同步块或方法中调用的原因,并通过代码示例展示了它们在实现线程间通信中的作用。此外,还探讨了Lock接口的Condition如何提供更灵活的线程通信方式,包括await()、signal()和signalAll()方法,以及如何实现定制化的线程通知。
摘要由CSDN通过智能技术生成

java多线程的wait()方法和notify()方法:

这两个方法是成对出现和使用的,要执行这两个方法,有一个前提就是,当前线程必须获其对象的monitor(俗称“锁”),否则会抛出IllegalMonitorStateException异常,所以这两个方法必须在同步块代码或者同步方法里面调用

wait():阻塞当前线程

notify():唤起被wait()阻塞的线程

wait()和notify()必须在synchronized代码块或者同步方法里写

synchronized的是哪个对象,调用wait()和notify()的就必须是哪个对象
eg:在方法声明上写synchronized,调用this.wait() 和this.notify
synchronized(obj) { obj.wait(); obj.notify();}

你是如何调用 wait() 方法的?使用 if 块还是循环?为什么?

处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。

wait() 方法应该在循环调用,因为当线程获取到 CPU 开始执行的时候,其他条件可能还没有满足,所以在处理前,循环检测条件是否满足会更好。下面是一段标准的使用 wait 和 notify 方法的代码:

synchronized (monitor) {
    //  判断条件谓词是否得到满足
    while(!locked) {
        //  等待唤醒
        monitor.wait();
    }
    //  处理其他的业务逻辑
}

为什么 wait(), notify()和 notifyAll()必须在同步方法或者同步块中被调用?

当一个线程需要调用对象的 wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的 notify()方法。同样的,当一个线程需要调用对象的 notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用

notify() 和 notifyAll() 有什么区别?

如果线程调用了对象的 wait()方法,那么线程便会处于该对象的等待池中,等待池中的线程不会去竞争该对象的锁

notifyAll() 会唤醒所有的线程,notify() 只会唤醒一个线程。

notifyAll() 调用后,会将全部线程由等待池移到锁池,然后参与锁的竞争,竞争成功则继续执行,如果不成功则留在锁池等待锁被释放后再次参与竞争。而 notify()只会唤醒一个线程,具体唤醒哪一个线程由虚拟机控制。

Java 如何实现多线程之间的通讯和协作?

可以通过中断 和 共享变量的方式实现线程间的通讯和协作

比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。

Java中线程通信协作的最常见的两种方式

一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()

二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()

在这里插入图片描述

在这里插入图片描述

线程通信代码演示:

package com.fan.sync;
//资源类,定义属相和操作资源的方法
class Share{
    //初始值
    private int number = 0;
    //操作资源的方法,+1的方法
    public synchronized void incr() throws InterruptedException {
        //第二步:判断,干活,通知
        while(number != 0){//什么情况下等,不是0,等待
            this.wait();
        }
        //如果number是0,就+1操作
        number++;
        //输出number结果
        System.out.println(Thread.currentThread().getName()+
                "::"+number);
        //通知其他线程
        this.notifyAll();
    }
    //-1的方法
    public synchronized void decr() throws InterruptedException {
        //判断,什么时候要等待,当等于1的时候-1,不等于1的时候就等一下
        while(number != 1){
            this.wait();
        }
        //干活
        number--;
        //输出number结果
        System.out.println(Thread.currentThread().getName()+
                "::"+number);
        //通知其他线程
        this.notifyAll();
    }
}

public class ThreadDemo {
    public static void main(String[] args) {
        Share share = new Share();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        },"aa").start();
        //
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        },"bb").start();
        //
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        },"cc").start();
        //
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        },"dd").start();
    }
}

运行结果;
在这里插入图片描述

如果把while换成if,则会出现虚假唤醒;

在这里插入图片描述

在这里插入图片描述

  • Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,他们必须用同一个Lock对象。锁是上在代表要操作资源的类的内部方法中,而不是线程代码中

  • Condition的功能类似在传统线程技术中的Object.wait和Object.notify的功能。在等待Condition时,允许发生“虚假唤醒”,这通常作为基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为Condition应该总是在一个循环中被等待,并测试整备等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待

  • 一个锁内部可以有多个Condition,即有多路等待和通知,可以看jdk1.5提供的Lock和Condition实现的可阻塞队列的应用案例,从中除了要体味算法,还要体味面向对象的封装。在传统的线程机制中一个监视器对象上只能有一路等待通知,要想实现多路等待通知,必须嵌套使用多个同步监视器对象。(如果只用一个Condition,两个放的都在等,一旦一个放的进去了,那么它通知可能会导致另一个放接着往下走)

  • 读写锁:分为读锁和写锁,多个读锁不互斥,读锁和写锁互斥,写锁与写锁互斥。这是JVM自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!

lock实现线程通信

Condition接口:

  • condition可以通俗的理解为条件队列。当一个线程在调用了await方法以后,直到线程等待的某个条件为真的时候才会被唤醒。这种方式为线程提供了更加简单的等待/通知模式。Condition必须要配合锁一起使用,因为对共享状态变量的访问发生在多线程环境下。一个Condition的实例必须与一个Lock绑定,因此Condition一般都是作为Lock的内部实现

  • await() :造成当前线程在接到信号或被中断之前一直处于等待状态

  • await(long time, TimeUnit unit) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态

  • awaitNanos(long nanosTimeout) :造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。返回值表示剩余时间,如果在nanosTimesout之前唤醒,那么返回值 = nanosTimeout - 消耗时间,如果返回值 <= 0 ,则可以认定它已经超时了

  • awaitUninterruptibly() :造成当前线程在接到信号之前一直处于等待状态。【注意:该方法对中断不敏感】

  • awaitUntil(Date deadline) :造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。如果没有到指定时间就被通知,则返回true,否则表示到了指定时间,返回返回false

  • signal() :唤醒一个等待线程。该线程从等待方法返回前必须获得与Condition相关的锁

  • signalAll() :唤醒所有等待线程。能够从等待方法返回的线程必须获得与Condition相关的锁

在这里插入图片描述

package com.fan.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//共享资源类,定义属性和操作资源的方法
class Share{
    private int number = 0;
    //创建lock
    private Lock lock = new ReentrantLock();
    //创建通信端口对象
    private Condition condition = lock.newCondition();
    //定义操作属性的方法
    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();//finally中解锁
        }

    }
}
public class ThreadConditionDemo {
    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();
                }
            }
        },"AA").start();
        //
        new Thread(()->{
            //调用任务
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();
        //
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    share.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"CC").start();
        //
        new Thread(()->{
            //调用任务
            for (int i = 0; i < 10; i++) {
                try {
                    share.decr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"DD").start();

    }
}

四个线程开始执行:

在这里插入图片描述

线程间定制化通信:

在这里插入图片描述

定制化通信代码:需要定义三个条件队列condition:

package com.fan.lock;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//第一步,创建资源类,定义属性和操作方法
class ShareRes{
    //定义标志位
    private int flag = 1;//1 AA,2 BB, 3 CC
    //创建lock锁
    private Lock lock = new ReentrantLock();
    //创建一个锁下的绑定的三个condition条件队列
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();
    //定义操作方法
    //打印5次,参数是第几轮
    public void print5(int loop) throws InterruptedException {
        //方法开始的时候就上锁
        lock.lock();
        try {
            //三部曲,判断,干活,通知
            while(flag != 1){//当flag==1的时候打印5次,则不等于1,等待
                c1.await();//c1等待
            }
            //干活
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName()+
                        "::"+i+":轮数:"+loop);
            }
            //通知,这里是定向通知,为何要先修改标志位,因为下一个线程也需要等待判断
            flag = 2;//修改标志位为2
            c2.signal();//c2条件队列被通知,即通知BB线程
        }finally {
            lock.unlock();//解锁
        }
    }
    //定义一个打印15次的方法
    public void print7(int loop){
        //同一个锁,上锁
        lock.lock();
        try {
            //三部曲,判断,干活,通知
            //1.判断时候时候要等待
            while(flag != 2){
                c2.await();//条件队列要等待
            }
            //2干活
            for (int i = 0; i < 7; i++) {
                System.out.println(Thread.currentThread().getName()+
                        "::"+i+":轮数:"+loop);
            }
            //3定向通知
            flag = 3;//标志位被修改成3,瞧见队列c3被通知
            c3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();//解锁
        }
    }
    //定义打印10次的方法
    public void print10(int loop) throws InterruptedException {
        //上锁:同一个锁
        lock.lock();
        try {
            //三部曲,判断,干活,通知
            //判断什么时候要等待
            while(flag != 3){
                c3.await();//c3要等待其他的
            }
            //干活
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName()+
                        "::"+i+":轮数:"+loop);
            }
            //定向通知下一个条件队列
            flag = 1;
            c1.signal();//通知AA线程
        }finally {
            lock.unlock();//解锁
        }
    }

}
public class LockConditionDemo2 {
    public static void main(String[] args) {
        //创建资源类对象
        ShareRes shareRes = new ShareRes();
        new Thread(()->{
            //调用资源类的操作方法
            for (int i = 0; i < 10; i++) {
                try {
                    shareRes.print5(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"AA").start();

        new Thread(()->{
            //调用资源类的操作方法
            for (int i = 0; i < 10; i++) {
                try {
                    shareRes.print7(i);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }

        },"BB").start();
        
        new Thread(()->{
            //调用资源类的操作方法
            for (int i = 0; i < 10; i++) {
                try {
                    shareRes.print10(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"CC").start();
    }
}

总结:
Lock + 【一个Condition + await + signal】 = synchronized + wait + notify = 只能单一通知,没得选
Lock + 【多个Condition + await + signal】= 定制化通知,想通知哪个就通知哪个

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值