Java线程通信

2019-10-13  本文已影响0人  JuneWool

线程通信

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

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看场景需要使用。
第一次写博客,排版乱糟糟,有很多知识点还没完全讲到,讲得不够详细,多多谅解。迈出一步,那也是进步。

上一篇下一篇

猜你喜欢

热点阅读