美文网首页
Java线程通信

Java线程通信

作者: JuneWool | 来源:发表于2019-10-13 23:15 被阅读0次

    线程通信

    线程通信指的是多个线程在运行的期间,相互之间的数据交互协作。

    1.通信方式

    实现多个线程直接的协作,涉及到的通信方式主要四类。
    1)文件共享
    2)网络共享
    3)共享变量
    4)JDK提供的线程协调API

    1.文件共享
     线程A写文件,线程B读取文件达到线程协作。
    2.网络共享
     线程A发送数据,线程B接受数据达到线程协作。
    3.共享变量
     利用内存的公共区域,共享变量。线程A修改变量,线程B读取变量达到线程协作。

    以上3种方式都是比较触及的,我们主要链接JDK给我提供的API

    2.线程协作 JDK API

    生产者消费者模型是线程协作的典型场景(线程阻塞、线程唤醒),我们以一个示例去理解
    示例:线程A提车,没有车,则不执行。线程B有车了,通知线程A继续执行

    2.1 suspend/resume

    suspend挂起线程,resume恢复线程执行。这两个API是Thread提供的。
    示例:

    public static Object benz = null;
    public void suspendResumeTest() throws Exception {
            // 启动线程
            Thread consumerThread = new Thread(() -> {
                if (benz == null) { // 如果没奔驰,则进入等待
                    System.out.println("1、进入等待");
                    Thread.currentThread().suspend();
                }
                System.out.println("2、提到车,回家");
            });
            consumerThread.start();
            // 3秒之后,拉来一辆奔驰
            Thread.sleep(3000L);
            benz = new Object();
            consumerThread.resume();
            System.out.println("3、通知消费者");
        }
    

    suspend/resume虽然是Thread提供的,因为很容易写出死锁的代码。 所以被弃用了。
    死锁示例1:在写同步代码的时候容易出现:suspend在挂起之后并不会释放锁

    public void suspendResumeDeadLockTest() throws Exception {
            // 启动线程
            Thread consumerThread = new Thread(() -> {
                if (benz == null) { // 如果没奔驰,则进入等待
                    System.out.println("1、进入等待");
                    // 当前线程拿到锁,然后挂起
                    synchronized (this) {
                        Thread.currentThread().suspend();
                    }
                }
                System.out.println("2、提到车,回家");
            });
            consumerThread.start();
            // 3秒之后,拉来一辆奔驰
            Thread.sleep(3000L);
            benz = new Object();
            // 争取到锁以后,再恢复consumerThread
            synchronized (this) {
                consumerThread.resume();
            }
            System.out.println("3、通知消费者");
        }
    

    这种情况下消费者如果拿到锁,消费者就挂起了,生产者要通知消费者必须要抢到锁,但是因为消费者抢到锁之后挂起,并没有释放锁,生产者是抢不到锁,这就死锁了。
    死锁示例2: API调用顺序:suspend在resume后执行死锁

    public void suspendResumeDeadLockTest2() throws Exception {
            // 启动线程
            Thread consumerThread = new Thread(() -> {
                if (benz == null) {
                    System.out.println("1、没奔驰,进入等待");
                    try { // 为这个线程加上一点延时
                        Thread.sleep(5000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 这里的挂起执行在resume后面
                    Thread.currentThread().suspend();
                }
                System.out.println("2、提到车,回家");
            });
            consumerThread.start();
            // 3秒之后,拉来一辆奔驰
            Thread.sleep(3000L);
            benz = new Object();
            consumerThread.resume();
            System.out.println("3、通知消费者");
            consumerThread.join();
        }
    

    这种情况下suspend在resume后执行,线程恢复不了执行,死锁了。

    2.2 wait/notify

    这一对API只能由统一对象锁的持有者调用,也就是写在同步代码块里面,否则抛异常。
    wait使当前线程等待,加入该对象的等待池,并释放锁。
    notify/notifyAll唤醒一个或所有正在等待这个对象的线程
    因为必须用在同步代码块里面, wait/notify针对锁的问题不存在了。但是顺序需要注意。
    示例:

    public void waitNotifyTest() throws Exception {
           // 启动线程
           new Thread(() -> {
               if (benz == null) { // 如果没奔驰,则进入等待
                   synchronized (this) {
                       try {
                           System.out.println("1、进入等待");
                           this.wait();
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
               }
               System.out.println("2、提到车,回家");
           }).start();
           // 3秒之后,拉来一辆奔驰
           Thread.sleep(3000L);
           benz = new Object();
           synchronized (this) {
               this.notifyAll();
               System.out.println("3、通知消费者");
           }
       }
    

    注意:虽然wait会释放锁,但是对顺序有要求,如果wait在notify之调用,线程就永远处于WAITING状态了。
    死锁示例:

    public void waitNotifyDeadLockTest() throws Exception {
           // 启动线程
           new Thread(() -> {
               if (benz == null) { // 如果没奔驰,则进入等待
                   try {
                       Thread.sleep(5000L);
                   } catch (InterruptedException e1) {
                       e1.printStackTrace();
                   }
                   synchronized (this) {
                       try {
                           System.out.println("1、进入等待");
                           this.wait();
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
               }
               System.out.println("2、提到车,回家");
           }).start();
           // 3秒之后,拉来一辆奔驰
           Thread.sleep(3000L);
           benz = new Object();
           synchronized (this) {
               this.notifyAll();
               System.out.println("3、通知消费者");
           }
       }
    

    这种情况下wait在notify后执行,线程收不到通知,死锁了。

    2.3 park/unpark

    park等待许可,unpark提供许可令牌,让线程继续执行,park/unpark没有顺序要求。
    多次unpark之后调用park,线程会直接执行,因为已经拿到许可令牌了。
    许可令牌不会叠加,多次调用park,只有第一次会拿到许可,继续执行,后续的调用则进入等待。
    示例:

    public void parkUnparkTest() throws Exception {
            // 启动线程
            Thread consumerThread = new Thread(() -> {
                if (benz == null) { // 如果没奔驰,则进入等待
                    System.out.println("1、进入等待");
                    LockSupport.park();
                }
                System.out.println("2、提到车,回家");
            });
            consumerThread.start();
            // 3秒之后,拉来一辆奔驰
            Thread.sleep(3000L);
            benz = new Object();
            LockSupport.unpark(consumerThread);
            System.out.println("3、通知消费者");
        }
    

    park/unpark不会释放锁,所以在同步代码块里面使用不当就容易死锁。
    死锁示例:

    public void parkUnparkDeadLockTest() throws Exception {
            // 启动线程
            Thread consumerThread = new Thread(() -> {
                if (benz == null) { // 如果没奔驰,则进入等待
                    System.out.println("1、进入等待");
                    // 当前线程拿到锁,然后挂起
                    synchronized (this) {
                        LockSupport.park();
                    }
                }
                System.out.println("2、提到车,回家");
            });
            consumerThread.start();
            // 3秒之后,拉来一辆奔驰
            Thread.sleep(3000L);
            benz = new Object();
            // 争取到锁以后,再恢复consumerThread
            synchronized (this) {
                LockSupport.unpark(consumerThread);
            }
            System.out.println("3、通知消费者");
        }
    

    消费者挂起之后没有释放锁,生产者永远获取不到锁,死锁。

    2.4 join

    有人说join也是一种,其实join底层使用的wait/notify

    结语

    虽然都是些简单例子,但是我们通过这些例子去看正确的操作,还有死锁的情况,我可以在平时写这类代码的时候可以避免踩坑。弃用的suspend/resume就不要用了,wait/notify、park/unpark看场景需要使用。
    第一次写博客,排版乱糟糟,有很多知识点还没完全讲到,讲得不够详细,多多谅解。迈出一步,那也是进步。

    相关文章

      网友评论

          本文标题:Java线程通信

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