• 线程基本通信机制


    一、线程基本通信机制

    1 wait和notify的用法

    wait和notify是Java最基本的线程间通信机制,体现了线程的交互,用于信息的传递。例如在生产者消费者模式中,利用阻塞和唤醒使不同线程之间配合实现业务逻辑。

    阻塞阶段--wait,调用对象的wait方法,线程进入WAITING状态,阻塞挂起,释放锁
    wait阻塞后,直到下面情况之一发生时,线程才会被唤醒。

    • 其他线程调用该对象的notify/notifyAll方法。
    • 带有超时参数的wait方法,发生超时。如果参数是0,则永久等待。
    • 调用线程中断interrupt()。线程在waiting状态,会自动响应中断,抛出中断异常。

    唤醒阶段 --notify/notifyAll

    • notify:随机唤醒一个线程
    • notifyAll:唤醒所有线程

    2 wait和notify性质

    1. 使用前需要拥有锁(monitor锁)
    2. 属于Object类,底层是final native方法,属于JVM层代码。
    3. wait和notify是最基本的线程通信机制
    4. 同时拥有多把锁,需要注意锁的释放顺序
    synchronized(this) { 
        while(条件){
           wait();     
        }
    }
    
    • 如果wait方法没有修饰,表示当前对象this
    • 即使没有调用唤醒方法,线程仍有可能从挂起状态变为可运行状态(虚假唤醒)。为防患于未然,常见是不断测试该线程被唤醒的条件是否被满足,不满足则继续等待。
    synchronized (resourceA) {
        synchronized (resourceB) {
            try {
                resourceA.wait(); // 只释放resourceA锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    

    注意点:调用某个对象的notify,只能唤醒与该对象对应的线程。调用wait也只释放当前的那把锁。

    3 wait原理

    EntrySet:入口集;WaitSet:等待集;The owner:线程拥有锁。’
    锁的运行原理:开始线程在入口集和等待集竞争锁【1】,此时线程A获取到了锁【2】,入口集和等待集中的线程进入BLOCKED。此时A可以正常运行完释放锁【6】,也可以调用wait释放锁进入等待集【3】。等待集线程被唤醒【4】后,进入另一个等待集,与入口集的线程一起竞争锁【5】。

    二、常见问题

    1 wait和notify实现生产者消费者模式

    生产者消费者模式可以解耦生产者和消费者,使两者更好地配合。

    // 生产和消费100个产品
    public class ProducerConsumerModelByWaitAndNotify {
        public static void main(String[] args) {
            // 创建仓库
            Storage storage = new Storage();
            // 创建生产者消费者线程
            Thread producer = new Thread(new ProducerTask(storage));
            Thread consumer = new Thread(new ConsumerTask(storage));
            producer.start();
            consumer.start();
        }
    }
    
    class ProducerTask implements Runnable {
        private Storage storage;
    
        public ProducerTask(Storage storage) {
            this.storage = storage;
        }
    
        @Override
        public void run() {
            // 生产100个产品
            for (int i = 0; i < 100; i++) {
                storage.put();
            }
        }
    }
    
    class ConsumerTask implements Runnable {
        private Storage storage;
    
        public ConsumerTask(Storage storage) {
            this.storage = storage;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                storage.take();
            }
        }
    }
    
    class Storage {
        private int maxSize;
        private Queue<Date> storage;
    
        public Storage() {
            this.maxSize = 10; // 队列最大是10
            this.storage = new LinkedList<>();
        }
    
        /**
         * wait和notify需要首先获取到锁,因此需要使用synchronized方法或者同步代码块
         */
        public synchronized void put() {
            // 仓库已满,无法生产更多产品,让出锁
            while (storage.size() == maxSize) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            storage.add(new Date());
            System.out.println("生产者生产产品,此时仓库产品数:" + storage.size());
            // 通知消费者消费
            notify();
        }
    
        public synchronized void take() {
            // 仓库为空,无法获取到产品,线程让出锁
            while (storage.size() == 0) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            storage.poll();
            System.out.println("消费者消费产品,此时仓库产品数:" + storage.size());
            // 通知生产者生产
            notify();
        }
    }
    

    2. 为什么wait需要放在同步代码块中使用,而sleep不需要?

    主要是为了让通信更加可靠,防止死锁、永久等待的发生。

    wait放到synchronized代码中对线程有一定的保护作用。假设没有synchronized的保护,线程A在运行到wait语句之前,切换到线程B执行了notify语句,此时执行了wait语句释放锁后,没有线程唤醒,导致了永久等待。

    sleep方法是针对单个线程的,与其他线程无关,无需放入到同步代码块中。

    3 为什么wait和notify方法定义在Object中,而不是Thread中?

    wait和notify是锁级别操作,而锁是属于某个对象的,锁标识在对象的对象头中。如果将wait和notify定义在线程中,则会有很大的局限性。例如每个线程都可能会休眠。如果某个线程持有多个锁,而且锁之间是相互配合的时,wait方法在Thread类中,就没有办法实现线程的配合。

    调用线程对象.wait会发生什么?

    个人理解:
    调用线程对象的wait方法,也就是说以线程为锁。wait和notify的初衷就是用来线程间通信,如果以线程为锁,不利于设计流程。

    版权声明:本文为博主原创文章,未经博主允许不得转载。
  • 相关阅读:
    node.js基础回顾
    PHP基础回顾之表单(二)
    PHP基础回顾(一)
    知识图谱Knowledge Graph
    Qt addStretch()详解
    Qt实现 QQ好友列表QToolBox
    Qt5
    用户级线程和内核级线程
    TCP状态转换图、滑动窗口、半连接状态、2MSL
    理解tcp顺序释放操作和tcp的半关闭
  • 原文地址:https://www.cnblogs.com/dtyy/p/14165662.html
Copyright © 2020-2023  润新知