4.线程通信

作者: 强某某 | 来源:发表于2020-03-03 15:03 被阅读0次

    线程协作-JDK API

    JDK中对于需要多线程协作完成某一任务的场景,提供了对应API支持。
    多线程协作的典型场景是:生产者-消费者模型。(线程阻塞,线程唤醒)

    示例:线程1去买包子,没有包子,则不再执行。线程-2生产包子,通知线程-1继续执行


    1.png
    1. 被弃用的suspend和resume
      作用:调用suspend挂起目标线程,通过resume可以恢复线程执行
    • 能正确执行的代码


      2.png
    • 那么为什么被弃用?

    因为容易写出死锁的代码,uspend挂起之后并不会释放锁

    错误示例有下面两种情况


    3.png
    1. wait/notify机制
      这些方法只能由同一对象锁的持有者线程调用,也就是写在同步代码块(例如synchronized中,而且锁对象要是同一个)里面,否则会抛出illegalMonitorStateException异常。

    wait方法导致当前线程等待,加入该对象(就是锁对象)的等待集合中,并且放弃当前持有的对象锁。
    notify/notifyAll方法唤醒一个或所有正在等待这个对象锁的线程。

    注意:虽然wait会自动解锁,但是对顺序还是有要求,如果在notify被调用之后,才开始wait方法的调用,线程会永远处于WAITING状态。


    4.png

    也就是说,使用wait/notify必须配合同步代码块

    1. park/unpark机制
      线程调用park则等待"许可",unpark方法为指定线程提供"许可"。
      不要求park和unpark方法的调用顺序。
      多次调用unpark之后,再调用park,线程会直接运行,但不会叠加(许可类似标记位,不会累加,每次park之后标记位重置,必须再unpark之后才能后续正确park),也就是说,连续多次调用park方法,第一次会拿到"许可"直接运行,后续调用会进入等待。
      但是:park不会使用锁,这是它的缺点。


      5.png
    1. 总结:
    • park/unpark机制:不会使用锁,但是顺序可变
    • wait/notify机制:wait释放锁,但是顺序有先后
    • suspend/resume机制:即不释放锁,也有先后顺序的问题
    1. 补充:伪唤醒


      6.png

    在JDK的官方的wait()方法的注释中明确表示线程可能被“虚假唤醒“,JDK也明确推荐使用while来判断状态信息。那么这种情况的发生的可能性有多大呢?

    使用生产者消费者模型来说明,伪唤醒造成的后果是本来未被唤醒的线程被唤醒了,那么就破坏了生产者消费者中的判断条件,也就是例子中的while条件number == 0或者number == 1。最终导致的结果就死0和1不能交替出现。

    JDK的两种同步方案均可能出现这种伪唤醒的问题(API说明明确表示会出现这种现象),这两种组合是synchronized+wait+notify和ReentrantLock+await+signal。下面的例子中,如果把while换成if,那么就0、1就不能交替出现,反制则会,例子中是100个线程进行增加,100个线程进行减少。

    在Java并发编程书上面引用了Thinking In Java的一句话说,大概意思是:任何并发编程都是通过加锁来解决。其实JDK并发编程也是通过加锁解决,每个对象都有一个对象锁,并且有一个与这个锁相关的队列,来实现并发编程。区别在于加锁的粒度问题,读读可以并发(在读远大于写的场景下比较合适),其他三种情况不能并发。而关系型数据库也是利用这一思想,只不过做得更加彻底,除了写写不能并发,其他三种情况都能并发,这得益于MVCC模型。

    public class Resource {
    
        public int number = 0;
    
        public synchronized void add() {
            while (number == 1) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            number++;
            System.err.println(number + "-" + Thread.currentThread().getId());
            notifyAll();
        }
    
        public synchronized void minus() {
            while (number == 0) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            number--;
            System.err.println(number + "-" + Thread.currentThread().getId());
            notifyAll();
        }
    
        public static class AddThread implements Runnable {
    
            private Resource resource;
    
            public AddThread(Resource resource) {
                this.resource = resource;
            }
    
            @Override
            public void run() {
                for (;;) resource.add();
            }
        }
    
        public static class MinusThread implements Runnable {
            private Resource resource;
    
            public MinusThread(Resource resource) {
                this.resource = resource;
            }
    
            @Override
            public void run() {
                for (;;) resource.minus();
            }
        }
    
        public static void main(String[] args) {
            Resource resource = new Resource();
            for (int i = 0; i < 100; i++) {
                new Thread(new AddThread(resource)).start();
                new Thread(new MinusThread(resource)).start();
            }
        }
    }
    
    public class ResourceLock {
    
        private int number = 0;
    
        private final ReentrantLock lock = new ReentrantLock();
        private final Condition condition = lock.newCondition();
    
        public int getNumber() {
            return this.number;
        }
    
        public void increase() {
            lock.lock();
            try {
                while (number == 1) {
                    condition.await();
                }
                number++;
                System.err.println(number + "-" + Thread.currentThread().getId());
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    
        public void decrease() {
            lock.lock();
            try {
                while (number == 0) {
                    condition.await();
                }
                number--;
                System.err.println(number + "-" + Thread.currentThread().getId());
                condition.signalAll();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    
        static class IncreaseThread implements Runnable {
            private ResourceLock resource;
    
            public IncreaseThread(ResourceLock resource) {
                this.resource = resource;
            }
    
            @Override
            public void run() {
                for (;;) resource.increase();
            }
        }
    
        static class DecreaseThread implements Runnable {
            private ResourceLock resource;
    
            public DecreaseThread(ResourceLock resource) {
                this.resource = resource;
            }
    
            @Override
            public void run() {
                for (;;) resource.decrease();
            }
        }
    
        public static void main(String[] args) throws Exception {
            ResourceLock resource = new ResourceLock();
            ExecutorService es = Executors.newFixedThreadPool(5);
            for (int i = 0; i < 100; i++) {
                es.submit(new IncreaseThread(resource));
                es.submit(new DecreaseThread(resource));
            }
        }
    
    }
    

    所以,伪唤醒的概率很大,建议书写相关代码的时候,用while替代if

    相关文章

      网友评论

        本文标题:4.线程通信

        本文链接:https://www.haomeiwen.com/subject/yeqhlhtx.html