并发编程 - 线程间三种常见的通信手段

线程间通信是指多个线程之间通过某种机制进行协调和交互,例如:线程等待和通知机制就是线程通讯的主要手段之一。

在 Java 中有以下三种实现线程等待的手段 :

  1. Object 类提供的 wait(),notify() 和 notifyAll() 方法;
  2. Condition 类下的 await(),signal()  和 signalAll() 方法;
  3. LockSupport 类下的 park() 和 unpark() 方法。

第一种方式的代码示例 : 

Object lock = new Object();
new Thread(() -> {
    synchronized (lock) {
        try {
            System.out.println("线程1 -> 进入等待");
            lock.wait();
            System.out.println("线程1 -> 继续执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程1 -> 执行完成");
    }
}).start();

Thread.sleep(1000);
synchronized (lock) {
    // 唤醒线程
    System.out.println("执行 notifyAll()");
    lock.notifyAll();
}

第二种方式的代码示例 :

// 创建 Condition 对象 (lock 可创建多个 condition 对象)
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
// 加锁
lock.lock();
try {
    // 一个线程中执行 await()
    condition.await();

    // 另一个线程中执行 signal()
    condition.signal();
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock();
}

上述两种线程间通信的方式其实是差不多的,只是 Condition 类它可以创建出多个对象。那为什么有了 Object 类的 wait 和 notify 的方式,还需要 condition 来干嘛呢 ?

答 :因为 Object 类的 wait 和 notify 只适用于一个任务队列,而 Condition 类的 await 和 signal 适用于多个任务队列,在多个任务队列的情况下,使用 Object 类的 wait 和 notify 可能会存在线程饿死的问题。

并发编程 - 线程间三种常见的通信手段_第1张图片比如以上这种生产者消费者模型,当生产者,消费者(阻塞式的)都有多个的时候,并且此时任务队列里面没有任务了,所以消费者就会进入休眠状态,此时生产者需要做两件事情 : 

  1. 将任务推送到任务队列
  2. 唤醒线程

【问题所在】

①  此时如果使用 Object 类提供的 wait 和 notify,而唤醒线程是存在两种可能的:

1)唤醒了消费者 

2)唤醒了生产者

        如果是唤醒了生产者,那就出问题了,当生产者这边代码执行完了就结束了,消费者这边永远不会去消费队列里的任务了,这就会导致线程饥饿问题。

②  而 Condition 类因为可以被创建多个,所以可以使用两个 Condition 对象,一个指定唤醒生产者,一个指定唤醒消费者,这样就不会出现线程饥饿了。

【结论】

所以 Condition 类的 await 和 signal 是对 Object 类的 wait 和 notify 的一个补充,它解决了 Object 类种分组不明确的问题。

第三种方式的代码示例:

public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        LockSupport.park();
        System.out.println("线程1被唤醒");
    },"线程1");
    t1.start();
    Thread t2 = new Thread(() -> {
        LockSupport.park();
        System.out.println("线程2被唤醒");
    },"线程2");
    t2.start();

    Thread t3 = new Thread(() -> {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("唤醒线程2");
        LockSupport.unpark(t2);
    },"线程3");
    t3.start();
}

LockSupport 类又是对 Condition 类的一个补充,它可以指定唤醒某一个线程,它解决了前两种方式不能随机指定唤醒线程的问题。

具体使用哪一种,还是要根据业务来进行选择~

你可能感兴趣的:(并发,开发语言,java)