线程间通信三要素:
多线程+判断+操作+通知+资源类。
上面的五个要素,其他三个要素就是普通的多线程程序问题,那么通信就需要线程间的互相通知,往往伴随着何时通信的判断逻辑。
在 java 的 Object 类里就提供了对应的方法来进行通知,同样的,保证安全的判断采用隐式的对象锁,也就是 synchronized 关键字实现。这块内容在:
java多线程:线程间通信——生产者消费者模型
已经写过。
二、使用 Lock 实现线程间通信
那么,我们知道 juc 包里提供了显式的锁,即 Lock 接口的各种实现类,如果想用显式的锁来实现线程间通信问题,唤醒方法就要使用对应的 Conditon 类的 await 和 signalAll 方法。(这两个方法名字也能看得出来,对应 Object 类的 wait 和 notifyAll)
Condition 对象的获取可以通过具体的 Lock 实现类的对象的 newCondition 方法获得。
public class Communication2 { public static void main(String[] args) { Container container = new Container(); new Thread(()->{ for (int i = 0; i < 10; i++){ container.increment(); } },"生产者1").start(); new Thread(()->{ for (int i = 0; i < 10; i++){ container.decrenment(); } },"消费者1").start(); new Thread(()->{ for (int i = 0; i < 10; i++){ container.increment(); } },"生产者2").start(); new Thread(()->{ for (int i = 0; i < 10; i++){ container.decrenment(); } },"消费者2").start(); new Thread(()->{ for (int i = 0; i < 10; i++){ container.increment(); } },"生产者3").start(); new Thread(()->{ for (int i = 0; i < 10; i++){ container.decrenment(); } },"消费者3").start(); } } class Container{ private int count = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void increment(){ lock.lock(); try { while (count != 0){ condition.await(); } count++; System.out.println(Thread.currentThread().getName() + " "+count); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void decrenment(){ lock.lock(); try{ while (count == 0){ condition.await(); } count--; System.out.println(Thread.currentThread().getName()+ " " + count); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }输出也没有任何问题。
这里面我们模拟了 3 个生产者和 3 个消费者,进行对一个资源类,其实就是一个数字 count 的操作,并使用 Condition 类来进行唤醒操作。
从目前代码的用法来看, juc 包的 Lock 接口实现类和之前使用 synchronized + Object 类的线程通信方法是一样的,但是并发包的开发工具给了他更多的灵活性。灵活在哪?
三、唤醒特定线程
新技术解决了旧问题,这个方法解决的问题就是,在生产者消费者问题里:有时候我们并不想唤醒所有的对面伙伴,而只想要唤醒特定的一部分,这时候该怎么办呢?
如果没有显式的 lock,我们的思路可能是:
采用一个标志对象,可以是一个数值或者别的;
当通信调用 signalAll 的时候,其他线程都去判断这个标志,从而决定自己应不应该工作。
这种实现是可行的,但是本质上其他线程都被唤醒,然后一直阻塞+判断,其实还是在竞争。那么 Condition 类其实就已经提供了对应的方法,来完成这样的操作:
我们看这样一个需求:
同样是多线程操作、需要通信。但是我们要指定各个线程交替的顺序,以及指定唤醒的时候是指定哪个具体线程,这样就不会存在唤醒所有线程然后他们之间互相竞争了。
具体说是:AA 打印 5 次,BB 打印 10 次, CC 打印 15次,然后接着从 AA 开始一轮三个交替。
代码如下:
public class TakeTurnPrint { public static void main(String[] args) { ShareResource resource = new ShareResource(); new Thread(()->{resource.printA();},"A").start(); new Thread(()->{resource.printB();},"B").start(); new Thread(()->{resource.printC();},"C").start(); } } /** * 资源 */ class ShareResource{ private int signal = 0;//0-A,1-B,2-C private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); public void printA(){ lock.lock(); try{ while (signal != 0){ condition.await();//精准 } for (int i = 0; i < 5; i++){ System.out.println(Thread.currentThread().getName() + " " + i); } signal = 1;//精准 condition1.signal();//精准指定下一个 }catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } public void printB(){ lock.lock(); try{ while (signal != 1){ condition1.await();//精准 } for (int i = 0; i < 10; i++){ System.out.println(Thread.currentThread().getName() + " " + i); } signal = 2;//精准 condition2.signal();//精准指定下一个 }catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } public void printC(){ lock.lock(); try{ while (signal != 2){ condition2.await();//精准 } for (int i = 0; i < 15; i++){ System.out.println(Thread.currentThread().getName() + " " + i); } signal = 0;//精准 condition.signal();//精准指定下一个 }catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } }其中,使用三个 Condition 对象,用一个 signal 的不同值,来通知不同的线程。