两个线程之间如何才能通信?我们来思考一个问题: 【编写两个线程,一个线程打印1 ~ 52,另一个线程打印字母A~Z,打印顺序为12A34B56C……5152Z,要求使用线程间的通信。】 要打印出这样的顺序,必须是线程1打印两个数字后暂停,通知线程2开始打印一个字母,然后再通知线程1…如此反复。
第一种解法主要是靠一个共享变量来做控制。主要的实现方式有:
最基本的synchronized、notify、wait。Lock和ConditionvolatileAtomicInteger下面我们用代码来实现一下:
volatile 修饰的变量值直接存在main memory里面,子线程对该变量的读写直接写入main memory,而不是像其它变量一样在local thread里面产生一份copy。 volatile 能保证所修饰的变量对于多个线程可见性,即只要被修改,其它线程读到的一定是最新的值。
public class Test { volatile int value = 2; public static void main(String[] args) { Test test = new Test(); test.thread1.start(); test.thread2.start(); } /** * 打印数字 */ public Thread thread1 = new Thread(() -> { for (int i = 1; i < 53; i = i + 2) { while (value == 1) { } System.out.print(i + "" + (i + 1)); value = 1; } }); /** * 打印字母 */ public Thread thread2 = new Thread(() -> { String[] inputArr = buildCharArr(26); for (String s : inputArr) { while (value == 2) { } System.out.print(s + ""); value = 2; } }); /** * 获取字母数组 * * @param max * @return */ public static String[] buildCharArr(int max) { String[] charArr = new String[max]; int tmp = 65; for (int i = 0; i < max; i++) { charArr[i] = String.valueOf((char) (tmp + i)); } return charArr; } }AtomicInteger类是系统底层保护的int类型,通过提供执行方法的控制进行值的原子操作。AtomicInteger它不能当作Integer来使用。主要用于在高并发环境下的高效程序处理。使用非阻塞算法来实现并发控制。
import java.util.concurrent.atomic.AtomicInteger; public class Test { AtomicInteger atomicInteger = new AtomicInteger(1); public static void main(String[] args) { Test test = new Test(); test.thread1.start(); test.thread2.start(); } /** * 打印数字 */ public Thread thread1 = new Thread(() -> { for (int i = 1; i < 53; i = i + 2) { while (atomicInteger.get() == 2) { } System.out.print(i + "" + (i + 1)); atomicInteger.set(2); } }); /** * 打印字母 */ public Thread thread2 = new Thread(() -> { String[] inputArr = buildCharArr(26); for (String s : inputArr) { while (atomicInteger.get() == 1) { } System.out.print(s + ""); atomicInteger.set(1); } }); /** * 获取字母数组 * * @param max * @return */ public static String[] buildCharArr(int max) { String[] charArr = new String[max]; int tmp = 65; for (int i = 0; i < max; i++) { charArr[i] = String.valueOf((char) (tmp + i)); } return charArr; } }CyclicBarrier可以实现让一组线程在全部到达Barrier时(执行await()),再一起同时执行,并且所有线程释放后,还能复用它,即为Cyclic。
举个例子,就像生活中我们会约朋友们到某个餐厅一起吃饭,有些朋友可能会早到,有些朋友可能会晚到,但是这个餐厅规定必须等到所有人到齐之后才会让我们进去。这里的朋友们就是各个线程,餐厅就是 CyclicBarrier。
CyclicBarrier类提供两个构造器:
public CyclicBarrier(int parties, Runnable barrierAction) { } public CyclicBarrier(int parties) { } parties 是参与线程的个数第二个构造方法有一个 Runnable 参数,这个参数的意思是最后一个到达线程要做的任务重要方法:
public int await() throws InterruptedException, BrokenBarrierException public int await(long timeout, TimeUnit unit) throws InterruptedException, BrokenBarrierException, TimeoutException 线程调用 await() 表示自己已经到达栅栏BrokenBarrierException 表示栅栏已经被破坏,破坏的原因可能是其中一个线程 await() 时被中断或者超时从上面介绍来看CyclicBarrier API并不适合开头的题目,有点杀猪用牛刀的感觉,实在要用也可以,但是这里我用一个更适合CyclicBarrier使用场景的例子:10个同学聚餐,必须等所有同学到齐才能开始吃饭。
import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class Test { static CyclicBarrier barrier = new CyclicBarrier(10, new Runnable() { @Override public void run() { System.out.println("老师说:“同学到齐,开始干饭吧!”"); } }); public static void main(String[] args) { for (int i = 1;i<11;i++){ int finalI = i; new Thread(new Runnable() { @Override public void run() { try { System.out.println("同学"+ finalI +"到了"); barrier.await(); System.out.println("同学"+ finalI +"开始干饭"); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } } }).start(); } } }输出结果:
同学9到了 同学5到了 同学4到了 同学3到了 同学2到了 同学10到了 同学8到了 同学6到了 同学1到了 同学7到了 老师说:“同学到齐,开始干饭吧!” 同学9开始干饭 同学3开始干饭 同学7开始干饭 同学5开始干饭 同学2开始干饭 同学6开始干饭 同学1开始干饭 同学8开始干饭 同学4开始干饭 同学10开始干饭