线程通信
前言
对于以下两种场景:
1.子线程执行完毕之后,通知主线程处理某些逻辑的场景。
2.线程A执行某个操作之后通知线程B执行某些操作。
这两种场景涉及到线程间通信,可以通过以下几种方式来实现。
1)等待通知机制
两个线程通过对同一对象调用等待 wait() 和通知 notify() 方法来进行通讯。
两个线程交替打印奇数偶数的示例:
1 public class NumberThreadTest { 2 3 private int start = 1; 4 5 private boolean flag = false; 6 7 public static void main(String[] args) { 8 NumberThreadTest numberThreadTest = new NumberThreadTest(); 9 OuNum ouNum = new OuNum(numberThreadTest); 10 Thread tdOne = new Thread(ouNum); 11 tdOne.setName("偶数线程A"); 12 Thread tdTwo = new Thread(new JiNum(numberThreadTest)); 13 tdTwo.setName("奇数线程B"); 14 tdOne.start(); 15 tdTwo.start(); 16 } 17 18 private static class OuNum implements Runnable { 19 20 private NumberThreadTest numberThreadTest; 21 22 public OuNum(NumberThreadTest numberThreadTest) { 23 this.numberThreadTest = numberThreadTest; 24 } 25 26 @Override 27 public void run() { 28 synchronized (NumberThreadTest.class) { 29 while (numberThreadTest.start <= 100) { 30 31 if (numberThreadTest.flag) { 32 System.out.println(Thread.currentThread().getName() + "打印值为偶数:" + numberThreadTest.start); 33 numberThreadTest.start++; 34 numberThreadTest.flag = false; 35 NumberThreadTest.class.notify(); 36 } else { 37 try { 38 NumberThreadTest.class.wait(); 39 } catch (InterruptedException e) { 40 e.printStackTrace(); 41 } 42 } 43 } 44 } 45 } 46 } 47 48 private static class JiNum implements Runnable { 49 50 private NumberThreadTest numberThreadTest; 51 52 public JiNum(NumberThreadTest numberThreadTest) { 53 this.numberThreadTest = numberThreadTest; 54 } 55 56 @Override 57 public void run() { 58 synchronized (NumberThreadTest.class) { 59 while (numberThreadTest.start <= 100) { 60 if (!numberThreadTest.flag) { 61 System.out.println(Thread.currentThread().getName() + "打印值为奇数:" + numberThreadTest.start); 62 numberThreadTest.start++; 63 numberThreadTest.flag = true; 64 NumberThreadTest.class.notify(); 65 } else { 66 try { 67 NumberThreadTest.class.wait(); 68 } catch (InterruptedException e) { 69 e.printStackTrace(); 70 } 71 } 72 } 73 } 74 } 75 } 76 }
这里的线程 A 和线程 B 都对同一个对象 NumberThreadTest
.class
获取锁,A 线程调用了同步对象的 wait() 方法释放了锁并进入 WAITING
状态。
B 线程调用了 notify() 方法,这样 A 线程收到通知之后就可以从 wait() 方法中返回。
这里利用了 NumberThreadTest.class
对象完成了通信。
等待通知有着一个经典范式:
线程 A 作为消费者:
- 获取对象的锁。
- 进入 while(判断条件),并调用 wait() 方法。
- 当条件满足跳出循环执行具体处理逻辑。
线程 B 作为生产者:
- 获取对象锁。
- 更改与线程 A 共用的判断条件。
- 调用 notify() 方法。
volatile 共享内存
CountDownLatch 并发工具
CountDownLatch使一个线程(主线程)等待其他线程(多个子线程)各自执行完毕后再执行该主线程。
通过一个计数器来实现的,计数器的初始值一般设定为子线程的数量。每当一个线程(子线程)执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有子线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了(主线程就可以继续执行了)。
1 public class CountDownTest { 2 private static final CountDownLatch countDownLatch = new CountDownLatch(2); 3 4 private static volatile int count = 0; 5 6 public static void main(String[] args) throws InterruptedException { 7 PrintJiNum printJiNum = new PrintJiNum(); 8 PrintOuNum printOuNum = new PrintOuNum(); 9 System.out.println("-------开始执行--------"); 10 new Thread(printJiNum).start(); 11 new Thread(printOuNum).start(); 12 countDownLatch.await(); 13 System.out.println("线程执行完成"); 14 } 15 16 private static class PrintJiNum implements Runnable { 17 @Override 18 public void run() { 19 System.out.println("打印奇数线程现在开始执行------"); 20 while (count <= 100) { 21 if (count % 2 == 0) { 22 System.out.println(Thread.currentThread().getName() + "------" + count); 23 count++; 24 } 25 } 26 countDownLatch.countDown(); 27 } 28 } 29 30 private static class PrintOuNum implements Runnable { 31 32 @Override 33 public void run() { 34 System.out.println("打印偶数线程现在开始执行------"); 35 while (count <= 100) { 36 if (count % 2 != 0) { 37 System.out.println(Thread.currentThread().getName() + "------" + count); 38 count++; 39 } 40 } 41 countDownLatch.countDown(); 42 } 43 } 44 }
CyclicBarrier 并发工具
CyclicBarrier 中文名叫做屏障或者是栅栏,也可以用于线程间通信。
它可以等待 N 个线程都达到某个状态后然后多个线程才可以继续运行。
- 首先初始化线程参与者。
- 调用
await()
将会在所有参与者线程都调用之前等待。 - 直到所有参与者都调用了
await()
后,所有线程从await()
返回继续后续逻辑。
1 private static void cyclicBarrier() throws Exception { 2 CyclicBarrier cyclicBarrier = new CyclicBarrier(3) ; 3 4 new Thread(new Runnable() { 5 @Override 6 public void run() { 7 LOGGER.info("thread run"); 8 try { 9 cyclicBarrier.await() ; 10 } catch (Exception e) { 11 e.printStackTrace(); 12 } 13 14 LOGGER.info("thread end do something"); 15 } 16 }).start(); 17 18 new Thread(new Runnable() { 19 @Override 20 public void run() { 21 LOGGER.info("thread run"); 22 try { 23 cyclicBarrier.await() ; 24 } catch (Exception e) { 25 e.printStackTrace(); 26 } 27 28 LOGGER.info("thread end do something"); 29 } 30 }).start(); 31 32 new Thread(new Runnable() { 33 @Override 34 public void run() { 35 LOGGER.info("thread run"); 36 try { 37 Thread.sleep(5000); 38 cyclicBarrier.await() ; 39 } catch (Exception e) { 40 e.printStackTrace(); 41 } 42 43 LOGGER.info("thread end do something"); 44 } 45 }).start(); 46 47 LOGGER.info("main thread"); 48 }
Semaphore并发工具
Semaphore也是一个线程同步的辅助类,可以维护当前访问自身的线程个数,并提供了同步机制。使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。
Semaphore的主要方法摘要:
void acquire():从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。
void release():释放一个许可,将其返回给信号量。
int availablePermits():返回此信号量中当前可用的许可数。
boolean hasQueuedThreads():查询是否有线程正在等待获取。
1 public class SemaphoreMutex { 2 //初始化为1,互斥信号量 3 private final static Semaphore mutex = new Semaphore(1); 4 5 public static void main(String[] args) { 6 ExecutorService pools = Executors.newCachedThreadPool(); 7 8 for (int i = 0; i < 10; i++) { 9 final int index = i; 10 Runnable run = new Runnable() { 11 @Override 12 public void run() { 13 try { 14 mutex.acquire(); 15 System.out.println(String.format("[Thread-%s]任务id --- %s--%s", 16 Thread.currentThread().getId(), index, LocalDateTime.now())); 17 TimeUnit.SECONDS.sleep(2); 18 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } finally { 22 //使用完成释放锁 23 mutex.release(); 24 System.out.println("锁释放"); 25 } 26 } 27 }; 28 pools.execute(run); 29 } 30 pools.shutdown(); 31 } 32 }