JAVA多线程(七)--线程通信

1. Java线程通信最常见的两种方式:
1)synchronized加锁的线程调用Object类的wait()/notify()/notifyAll()
2)ReentrantLock类加锁的线程调用Condition类的await()/signal()/signalAll()
2.先来看第一种方式。
wait()/notify()/notifyAll()
1) wait()/notify()/notifyAll()是native,是final不能被重写
2)wait方法可以使得当前线程阻塞,并且当前线程必须拥有对象的monitor
3)notify能够唤醒一个正在等待该对象的mointor lock的线程,这个方法只能唤醒其中一个线程
4)notifyAll能够唤醒所有正在等待该对象的mointor lock的线程
需要注意的是:
1)使用wait没有获取到该对象的monitor lock将会抛出IllegalMonitorStateException,我们看一个例子:

public class GY{
   public static void main(String[] args) {
       String lock = new String("test");
       try {
           lock.wait();
       } catch (InterruptedException e) {
           e.printStackTrace();
       }
   }
}

在这里插入图片描述
2)调用某个对象的wait方法会使得当前线程阻塞,wait方法之后的代码将不会去执行
再看一个例子:

public class GY{
    public static void main(String[] args) {
        String lock = new String("test");
        synchronized (lock) {
            try {
                lock.wait();
                System.out.println("the current thread is "+Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

在这里插入图片描述
3)wait方法调用之后会释放掉当前对象的锁
4) notify方法唤醒等待当前对象锁的一个线程,但是该方法执行之后并不会立即释放锁,出了同步代码块才会将锁释放
验证3,4,再看一个例子

public class GY{
    public static void main(String[] args) {
        String lock = new String("test");
                new Thread("A"){
            @Override
            public void run() {
                synchronized (lock){
                    try {
                        lock.wait();
                        System.out.println("the current thread is "+Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();

        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread("B"){
            @Override
            public void run() {
                synchronized (lock){
                    lock.notify();
                    System.out.println("the current thread is "+Thread.currentThread().getName());
                }
            }
        }.start();
    }
}

在这里插入图片描述
5)wait方法是可中断方法,可中断方法会收到中断异常InterruptedException,同时interrupt标识会被擦除。
对5的验证:

public class GY{
    public static void main(String[] args) {
        String lock = new String("test");
        Thread threadA = new Thread("A"){
            @Override
            public void run() {
                synchronized (lock){
                    try {
                        lock.wait();
                        System.out.println("the current thread is "+Thread.currentThread().getName());
                    } catch (InterruptedException e) {
                        System.out.println("the thread has been interrupted and the flag is "+Thread.currentThread().isInterrupted());
                    }
                }
            }
        };
        threadA.start();
        threadA.interrupt();

    }
}

在这里插入图片描述
同时一个疑问:wait()/notify()/notifyAll()为什么定义在Object中?
原因就是每个对象都有monitor,让当前线程等待某个对象的monitor lock,当然就需要通过该对象来操作,而不是通过当前线程去操作。
2.1.wait()和sleep()方法之间的区别和联系
从表面上看,wait()和sleep()都可以使得当前线程进入阻塞状态,但是两者之间存在本质性的差别,下面总结两者的区别和相似之处:
1)wait()和sleep()都可以使得线程进入阻塞状态
2) wait()和sleep()都是可中断方法,被中断之后都会收到中断异常
3) wait()是Object类中的方法,由于wait()调用必须在一个synchronized方法/块中,调用之前需要先获取对象的monitor,每个对象都有自己的monitor,让当前线程等待当前对象的monitor,当然需要当前对象来操作,所以wait()方法就必须要定义在Object类中, 而sleep()是Thread特有的方法
4) wait()方法执行必须在同步方法/同步块中,而sleep()不需要
5) 线程在同步方法中执行sleep()时,并不会释放掉monitor的锁,而wait()会释放掉
6) Sleep()短暂休眠之后会主动退出阻塞,而wait()(没有指定时间)则需要被其他线程中断后/其他线程唤醒并获取到当前对象的monitor后才能退出阻塞
2.2.生产者-消费者模型
生产者:生产商品 - 某一个地方
消费者:消费商品 - 某一个地方
在这里的某一个地方,在JAVA中就是队列:该队列需要可阻塞的put/take方法,如果队列已经满了那么put方法将阻塞直到有空间可用,如果队列已经空了,那么take方法将会阻塞直到有元素可用。
在代码的实现中:
队列 - 共享资源
生产者 - 生产数据,将数据放入到队列中
生产者 - 消费数据,从队列中消费数据

class SharingQueue<E>{
    private final LinkedList<E> queue = new LinkedList<>();
    private static final int defaultMaxValue = 5;
    private final int max;

    public SharingQueue(){
        this(defaultMaxValue);
    }

    public SharingQueue(int size){
        this.max = size;
    }

    public void put(E value){
        synchronized (queue){
           if(queue.size() >= max){
                System.out.println(Thread.currentThread().getName() + ": queue is full!!!");
                try {
                    queue.wait();//使得当前线程进入阻塞状态
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ": the new data " + value+" has been produduted");
            queue.addLast(value);
            queue.notify();
        }
    }

    public E take(){
        synchronized (queue){
            if(queue.isEmpty()){
                System.out.println(Thread.currentThread().getName() + ": queue is empty!!!");
                try {
                    queue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            E result = queue.removeFirst();
            queue.notifyAll();//不会立即释放锁
            System.out.println(Thread.currentThread().getName() + ": the data" + result+" has been handled");
            return result;
        }
    }
}

public class GY{
    public static void main(String[] args) {
        SharingQueue<Integer> queue = new SharingQueue<>();

        new Thread("Producer"){
            @Override
            public void run() {
              while(true){
                    queue.put((int)(1+Math.random()*1000));
                }
            }
        }.start();

        new Thread("Consumer"){
            @Override
            public void run() {
                while(true){
                    queue.take();

                    try {
                        TimeUnit.MILLISECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

目前这个例子只是单线程,但是如果是多个线程的时候就会出现问题,也就是单线程通信的生产者消费者模型可能会出现的问题:
1)queue中没有元素仍然调用了removeFirst方法
threadA , threadB - take 陷入了阻塞,threadC - put 唤醒threadA, threadA执行
notify方法唤醒threadB
2)queue中元素超过阀值仍然调用了addLast方法
对这个问题的改进就是:将临界值更改为while判断 ,将notify改为notifyAll,改进后的代码如下:

class SharingQueue<E>{
    private final LinkedList<E> queue = new LinkedList<>();
    private static final int defaultMaxValue = 5;
    private final int max;

    public SharingQueue(){
        this(defaultMaxValue);
    }

    public SharingQueue(int size){
        this.max = size;
    }

    public void put(E value){
        synchronized (queue){
           while(queue.size() >= max){
                System.out.println(Thread.currentThread().getName() + ": queue is full!!!");
                try {
                    queue.wait();//使得当前线程进入阻塞状态
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ": the new data " + value+" has been produduted");
            queue.addLast(value);
            queue.notifyAll();
        }
    }

    public E take(){
        synchronized (queue){
           while(queue.isEmpty()){
                System.out.println(Thread.currentThread().getName() + ": queue is empty!!!");
                try {
                    queue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            E result = queue.removeFirst();
            queue.notifyAll();//不会立即释放锁
            System.out.println(Thread.currentThread().getName() + ": the data" + result+" has been handled");
            return result;
        }
    }
}

public class GY{
    public static void main(String[] args) {
        SharingQueue<Integer> queue = new SharingQueue<>();

        new Thread("Producer"){
            @Override
            public void run() {
              while(true){
                    queue.put((int)(1+Math.random()*1000));
                }
            }
        }.start();

        new Thread("Consumer"){
            @Override
            public void run() {
                while(true){
                    queue.take();

                    try {
                        TimeUnit.MILLISECONDS.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
    }
}

在这里我们为什么要改为notifyAll()呢,首先要知道两个概念:
a.锁池:threadA拥有某个对象的monitor,而其他线程想要调用这个对象synchronzied方法(synchronzied块),由于这些线程在进入对象的synchronzied方法(synchronzied块)之前必须先获得该对象的monitor lock,但是此时已经被threadA占有,所以这些线程就会进入到该对象的锁池中。
b.等待池:threadA调用了某个对象的wait方法,threadA就会释放该对象的锁后,进入该对象的等待池中。
2.3.下来我们再来介绍原因,也就是notify()和notifyAll()的区别与联系:
1)notify只是唤醒一个正在wait当前对象锁的线程,而notifyAll唤醒所有。值得注意的是:notify是本地方法,具体唤醒哪一个线程由虚拟机控制;如果有多个线程等待,则线程规划器任意挑选出其中一个wait()状态的线程来发出通知
2)当有线程调用了对象的notifyAll()方法(唤醒所有wait线程)或 notify()方法(只随机唤醒一个 wait 线程),被唤醒的的线程便会进入该对象的锁池中,锁池中的线程会去竞争该对象锁。也就是说,调用了notify后只有一个线程会由等待池进入锁池,而notifyAll会将该对象等待池内的所有线程移动到锁池中,等待锁竞争
3) 调用notify和notifyAll方法后,当前线程并不会立即放弃锁的持有权,而必须要等待当前同步代码块执行完才会让出锁
4) 优先级高的线程竞争到对象锁的概率大,假若某线程没有竞争到该对象锁,它还会留在锁池中,唯有线程再次调用wait()方法,它才会重新回到等待池中。而竞争到对象锁的线程则继续往下执行,直到执行完了synchronized代码块,它会释放掉该对象锁,这时锁池中的线程会继续竞争该对象锁。
3.再看第二种方式:await()/signal()/signalAll()
我们可以知道Condition代替了Object当中的wait()/notify()/notifyAll()方法,以此实现线程间通信;Condition是一个接口, 基本方法是await()/signal()/signalAll();Condition依赖Lock接口,生成一个Condition对象需要lock.newCondition()得到Condition对象,调用await()/signal()/signalAll()。
需要注意:
1)使用await没有使用lock.lock()加锁将会抛出IllegalMonitorStateException
验证:

public class TestDemo {
    public static void main(String[] args) {
        //创建Lock对象
ReentrantLock lock = new ReentrantLock();
    //得到condition对象
    Condition con = lock.newCondition();
new Thread("A"){
@Override
public void run() {
        try {
        con.await();
        System.out.println("the current thread is " +Thread.currentThread().getName());
        } catch (InterruptedException e) {
        e.printStackTrace();
        }

        }
        }.start();
    }
}

在这里插入图片描述
2) await是可中断方法,可中断方法会收到中断异常InterruptedException,同时interrupt标识会被擦除
3)调用某个Condition对象的await方法会使得当前线程阻塞,调用await方法之后的代码将不会执行
4)await方法之后释放掉当前的lock
验证2)3)4)

public class TestDemo {
    public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
    Condition con = lock.newCondition();
new Thread("A"){
@Override
public void run() {
        lock.lock();
        try {
        con.await();
        System.out.println("the current thread is "+Thread.currentThread().getName());
        } catch (InterruptedException e) {
        e.printStackTrace();
        } finally {
        lock.unlock();
        }
        }
        }.start();

        try {
        TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        e.printStackTrace();
        }

        new Thread("B"){
@Override
public void run() {
        try{
        lock.lock();
        con.signal();
        System.out.println("the current thread is "+Thread.currentThread().getName());
        } finally {
        lock.unlock();
        }
        }
        }.start();

    }
}

在这里插入图片描述
5)signal方法唤醒等待当前锁的一个线程,但是该方法执行后不会立即释放锁,出了同步块才会将锁释放。
验证:

public class TestDemo {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition con = lock.newCondition();
        Thread threadA = new Thread("A"){
@Override
public void run() {
        lock.lock();
        try {
        con.await();
        System.out.println("the current thread is "+Thread.currentThread().getName());
        } catch (InterruptedException e) {
        System.out.println("the thread has been interrupted and the flag is "+Thread.currentThread().isInterrupted());
        } finally {
        lock.unlock();
        }
        }
        };
        threadA.start();
        threadA.interrupt();
    }
}

在这里插入图片描述
**4.提问:**await方法使用绑定在一个Condition对象上,一个ReentantLock对象可以拥有多个Condition对象,这些对象有什么作用?
答案就是wait只能在synchronized同步的对象中使用一个等待队列,而await对应的Lock可以有多个Condition对象,也就是说一个Lock可以有多个等待队列,相当于一个房子有多个门; synchronized块或者方法对于一个对象的一个queue,如果存在多个操作,比如isFull,isEmpty,add,remove,这样会涉及到上下文切换的性能损耗和获取锁的性能损耗。

4.使用await()/signal()/signalAll()实现生产者消费者模型

class EventQueue<E>{

    private final LinkedList<E> queue = new LinkedList<>();
    private static final int defaultMaxValue = 5;
    private final int max;

    //创建Lock对象
    private final Lock lock = new ReentrantLock();
    //创建一个关于put的Condition
    private Condition putCon = lock.newCondition();
    //创建一个关于take的Condition
    private Condition takeCon = lock.newCondition();

    public EventQueue(){
        this(defaultMaxValue);
    }

    public EventQueue(int size){
        this.max = size;
    }

    public void put(E value){
        try{
            lock.lock();
            while(queue.size() >= max){
                System.out.println(Thread.currentThread().getName() + ": queue is full!!!");
                putCon.await();
            }
            System.out.println(Thread.currentThread().getName() + ": the new data " + value+" has been produduted");
            queue.addLast(value);
            takeCon.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void take(){
        try{
            lock.lock();
            while(queue.isEmpty()){
                System.out.println(Thread.currentThread().getName() + ": queue is empty!!!");
                takeCon.await();
            }
            E result = queue.removeFirst();
            putCon.signalAll();
            System.out.println(Thread.currentThread().getName() + ": the data" + result+" has been handled");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class TestDemo {
    public static void main(String[] args) {
        EventQueue<Integer> queue = new EventQueue<>();
        for(int i=0; i<3; i++){
            new Thread("Producer-"+i){
                @Override
                public void run() {
                    while(true){
                        queue.put((int)(1+Math.random()*1000));
                    }
                }
            }.start();

            new Thread("Consumer-"+i){
                @Override
                public void run() {
                    while(true){
                        queue.take();

                        try {
                            TimeUnit.MILLISECONDS.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }.start();
        }
    }
}

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值