ReentrantLock,ReadWriteLock,CountdownLatch,CyclicBarrier,Phaser,Semaphore,Exchanger

一.各种锁的作用:

如果用了很多锁后,担心死锁逻辑,可以用jstack检查
ReentrantLock和Synchronized相比,最大的特点就是可以根据业务需求灵活控制.
ReentrantReadWriteLock用于一块资源读多写少,又要求避免脏数据的情况
Semaphore用于限流,控制最多同时执行的线程数.
剩下的主要是面试时候吊打面试官(手动狗头):
CountdownLatch和CyclicBarrier类似,需要准备大批量的数据/资源调动,分多个线程去准备,但是等这些数据都准备OK后才能进行下一步动作.(MapReduce?)
CountdownLatch是数到0就完事了,再往下数啥效果都没有,不能循环使用;
CyclicBarrier可以循环使用.
Exchanger可能写游戏的两人交易时用到
Phaser可能在遗传算法时用到

二.ReentrantLock(重要)

什么是可重入锁?字面意思, 可重入锁就是对同样的一把锁可以再锁一次.当一个线程中执行到加锁代码时,发现当前持有这把锁的线程就是自己,那可以执行该代码.
(ReentrantLock内部使用了CAS,有没有使用系统级别锁呢?回头再探究)

三.ReentrantLock特点|使用场景

1.简单使用,替代synchronized
2.可以tryLock(一定时间)
3.可以被马上打断lockInterruptibly
4.可以使用公平锁

简单使用,替代synchronized

这个可以替代synchronized,只是需要手动上锁(lock)和解锁(unlock),要注意lock操作以及接下来的业务代码用try{}包起来,在finally{}中unlock,无论是否有异常,都会释放锁.
使用synchronized时,当加锁代码执行完或者抛异常时,JVM会自动释放锁.

  1. public class T02_ReentrantLock2 {
  2. Lock lock = new ReentrantLock();
  3. void m1() {
  4. try {
  5. lock.lock(); //synchronized(this)
  6. for (int i = 0; i < 10; i++) {
  7. TimeUnit.SECONDS.sleep(1);
  8. System.out.println(i);
  9. // 此处验证一下"可重入"特性
  10. if (i == 2) {
  11. m2();
  12. }
  13. }
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. } finally {
  17. lock.unlock();
  18. }
  19. }
  20. void m2() {
  21. try {
  22. lock.lock();
  23. System.out.println("m2 ...");
  24. } finally {
  25. lock.unlock();
  26. }
  27. }
  28. public static void main(String[] args) {
  29. T02_ReentrantLock2 rl = new T02_ReentrantLock2();
  30. new Thread(rl::m1).start();
  31. try {
  32. TimeUnit.SECONDS.sleep(1);
  33. } catch (InterruptedException e) {
  34. e.printStackTrace();
  35. }
  36. // 必须等锁被上一个线程释放后,才能继续执行
  37. new Thread(rl::m2).start();
  38. }
  39. }

可以有不同的condition

每一个condition可以理解为一个等待队列.
synchronized中,线程wait后,其实是进了一个等待队列,等待被唤醒;当调用notify时随机唤醒等待队列其中一个线程,当调用notifyall时,唤醒等等待线程中的所有线程.
synchronized只有一个默认的等待对了,而ReentrantLock可以有多个等待队列,即多个condition.

看一个生产者/消费者的例子:

  1. /**
  2. * 写一个固定容量同步容器,拥有put和get方法,以及getCount方法,
  3. * 能够支持2个生产者线程以及10个消费者线程的阻塞调用
  4. */
  5. public class WzContainerReentrantLock<T> {
  6. private int max = 10;
  7. private int size = 0;
  8. private LinkedList<T> values = new LinkedList<>();
  9. private Lock lock = new ReentrantLock();
  10. private Condition producerCondition = lock.newCondition();
  11. private Condition consumerCondition = lock.newCondition();
  12. public T get() {
  13. T t = null;
  14. try {
  15. lock.lock();
  16. while (size == 0) {
  17. consumerCondition.await();
  18. }
  19. t = values.removeFirst();
  20. size--;
  21. producerCondition.signalAll();// 通知生产者
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. } finally {
  25. lock.unlock();
  26. }
  27. return t;
  28. }
  29. public void put(T t) {
  30. try {
  31. lock.lock();
  32. while (size == this.max) {
  33. producerCondition.await();
  34. }
  35. values.addLast(t);
  36. size++;
  37. consumerCondition.signalAll();// 通知消费者
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. } finally {
  41. lock.unlock();
  42. }
  43. }
  44. public static void main(String[] args) {
  45. WzContainerReentrantLock<String> container = new WzContainerReentrantLock<>();
  46. //启动消费者线程
  47. for (int i = 0; i < 10; i++) {
  48. new Thread(() -> {
  49. for (int j = 0; j < 4; j++) {
  50. String s = container.get();
  51. System.out.println(s);
  52. }
  53. }, "consumer-" + i).start();
  54. }
  55. try {
  56. TimeUnit.SECONDS.sleep(2);
  57. } catch (InterruptedException e) {
  58. e.printStackTrace();
  59. }
  60. //启动生产者线程
  61. for (int i = 0; i < 2; i++) {
  62. new Thread(() -> {
  63. for (int j = 0; j < 20; j++) {
  64. container.put(Thread.currentThread().getName() + " at " + Instant.now());
  65. }
  66. }, "producer-" + i).start();
  67. }
  68. }
  69. }

可以tryLock(一定时间)

  1. public class T03_ReentrantLock3 {
  2. Lock lock = new ReentrantLock();
  3. void m1() {
  4. try {
  5. lock.lock();
  6. for (int i = 0; i < 10; i++) {
  7. TimeUnit.SECONDS.sleep(1);
  8. System.out.println(i);
  9. }
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. } finally {
  13. lock.unlock();
  14. }
  15. }
  16. /**
  17. * 使用tryLock进行尝试锁定,不管锁定与否,方法都将继续执行
  18. * 可以根据tryLock的返回值来判定是否锁定
  19. * 也可以指定tryLock的时间,由于tryLock(time)抛出异常,所以要注意unclock的处理,必须放到finally中
  20. */
  21. void m2() {
  22. // 当然也可以lock.tryLock(),不指定时间
  23. /*
  24. boolean locked = lock.tryLock();
  25. System.out.println("m2 ..." + locked);
  26. if(locked) lock.unlock();
  27. */
  28. boolean locked = false;
  29. try {
  30. long start = System.currentTimeMillis();
  31. locked = lock.tryLock(5, TimeUnit.SECONDS);
  32. long end = System.currentTimeMillis();
  33. System.out.println("m2 ..." + locked + "|waiting time:" + (end - start));
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. } finally {
  37. if(locked) {
  38. lock.unlock();
  39. }
  40. }
  41. }
  42. public static void main(String[] args) {
  43. T03_ReentrantLock3 rl = new T03_ReentrantLock3();
  44. new Thread(rl::m1).start();
  45. try {
  46. TimeUnit.SECONDS.sleep(1);
  47. } catch (InterruptedException e) {
  48. e.printStackTrace();
  49. }
  50. new Thread(rl::m2).start();
  51. }
  52. }

可以被马上打断lockInterruptibly

lock()方法的锁,只能在当前线程获取到锁后才能被打断;
而lockInterruptibly()可以马上被打断,不必等获取锁.
证明一下:

  1. public class T04_ReentrantLock4 {
  2. public static void main(String[] args) {
  3. Lock lock = new ReentrantLock();
  4. Thread t1 = new Thread(()->{
  5. try {
  6. lock.lock();
  7. System.out.println("t1 start");
  8. TimeUnit.SECONDS.sleep(10);
  9. System.out.println("t1 end");
  10. } catch (InterruptedException e) {
  11. System.out.println("t1 was interrupted!");
  12. } finally {
  13. lock.unlock();
  14. }
  15. });
  16. t1.start();
  17. Thread t2 = new Thread(()->{
  18. try {
  19. // 如果是直接lock的话,t2就不能被直接打断,只能等t1执行完了,t2获取到lock后才能被打断
  20. // lock.lock();
  21. lock.lockInterruptibly(); //可以对interrupt()方法做出响应
  22. System.out.println("t2 start");
  23. TimeUnit.SECONDS.sleep(5);
  24. System.out.println("t2 end");
  25. } catch (InterruptedException e) {
  26. System.out.println("t2 was interrupted!");
  27. } finally {
  28. lock.unlock();
  29. }
  30. });
  31. t2.start();
  32. try {
  33. TimeUnit.SECONDS.sleep(1);
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. t2.interrupt(); //打断线程2的等待
  38. }
  39. }

公平锁

ReentrantLock默认为非公平锁,多个线程在一个队列中去竞争获取锁,随机拿到锁,就跟买彩票一样,不是先买的就先中奖(synchronized也是非公平锁);
公平锁就是像买饭排队一样,比较”公平”.

ReentrantLock可以指定是否为公平锁,默认非公平锁;
synchronized只有非公平锁

这个不太好写测试用例,回头再证明吧…
创建ReentrantLock时,传入fair参数为true,创建的就是公平锁

  1. public class T05_ReentrantLock5 extends Thread {
  2. //参数为true表示为公平锁,请对比输出结果
  3. private static ReentrantLock lock = new ReentrantLock(true);
  4. @Override
  5. public void run() {
  6. for (int i = 0; i < 100; i++) {
  7. System.out.println(Thread.currentThread().getName() + " will lock at" + System.currentTimeMillis());
  8. lock.lock();
  9. try {
  10. System.out.println(Thread.currentThread().getName() + " get lock at" + System.currentTimeMillis());
  11. } finally {
  12. lock.unlock();
  13. }
  14. }
  15. }
  16. public static void main(String[] args) throws InterruptedException {
  17. T05_ReentrantLock5 rl = new T05_ReentrantLock5();
  18. Thread th1 = new Thread(rl);
  19. Thread th2 = new Thread(rl);
  20. th1.start();
  21. th2.start();
  22. }
  23. }

ReadWriteLock读写锁(重要)

读写锁是读锁和写锁的合称,

读锁:共享锁,其他线程可以同时读,在读操作之间共享资源;但是其他线程不能写,否则会读到脏数据
写锁:排他锁/互斥锁,其他线程既不能写也不能读,也是为了防止脏数据.
其他的锁一般都是排它锁/互斥锁,当一个线程拿到锁后,别的线程就只能阻塞等待,啥也干不了.

主要用于一些内容,写操作不多,但是读操作很多的地方,比如公司的组织架构

读写锁代码示例:

  1. public class T10_TestReadWriteLock {
  2. static Lock lock = new ReentrantLock();
  3. private static int value;
  4. static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  5. static Lock readLock = readWriteLock.readLock();
  6. static Lock writeLock = readWriteLock.writeLock();
  7. public static void read(Lock lock, CountDownLatch countDownLatch) {
  8. try {
  9. lock.lock();
  10. //模拟读取操作
  11. Thread.sleep(1000);
  12. countDownLatch.countDown();
  13. System.out.println("read over!");
  14. } catch (InterruptedException e) {
  15. e.printStackTrace();
  16. } finally {
  17. lock.unlock();
  18. }
  19. }
  20. public static void write(Lock lock, int v, CountDownLatch countDownLatch) {
  21. try {
  22. lock.lock();
  23. // 写操作,模拟耗时1秒
  24. Thread.sleep(1000);
  25. value = v;
  26. countDownLatch.countDown();
  27. System.out.println("write over!");
  28. } catch (InterruptedException e) {
  29. e.printStackTrace();
  30. } finally {
  31. lock.unlock();
  32. }
  33. }
  34. public static void main(String[] args) throws InterruptedException {
  35. // 使用CountDownLatch方便计时,下面有CountdownLatch说明
  36. CountDownLatch countDownLatch = new CountDownLatch(20);
  37. // 普通互斥锁
  38. // Runnable readR = ()-> read(lock, countDownLatch);
  39. // Runnable writeR = ()->write(lock, new Random().nextInt(), countDownLatch);
  40. // 读写锁
  41. Runnable readR = () -> read(readLock, countDownLatch);
  42. Runnable writeR = () -> write(writeLock, new Random().nextInt(), countDownLatch);
  43. long start = System.currentTimeMillis();
  44. for (int i = 0; i < 18; i++) {
  45. new Thread(readR).start();
  46. }
  47. for (int i = 0; i < 2; i++) {
  48. new Thread(writeR).start();
  49. }
  50. countDownLatch.await();
  51. long end = System.currentTimeMillis();
  52. System.out.println("cust milliseconds:" + (end - start));
  53. }
  54. }

Semaphore信号量

有点类似于令牌桶算法.用于控制并发的最大数量,限流.
有时候一个任务的某一步骤特别耗时,很多个线程同时执行的话,每个线程都很慢,可能大家都超时失败,为了防止这一现象,可以用信号量控制最大并发数.
假设希望最大并发数为n,那就创建一个有n个令牌的桶,每次线程执行特定方法时,去申请令牌(acquire),拿不到就阻塞;拿到了才继续执行,执行完再把令牌放回桶里(release).

代码示例:

  1. public class T11_TestSemaphore {
  2. public static void main(String[] args) {
  3. // Semaphore s = new Semaphore(3);
  4. Semaphore s = new Semaphore(3, true);
  5. //允许一个线程同时执行
  6. // Semaphore s = new Semaphore(1);
  7. for (int i = 1; i <= 10; i++) {
  8. new Thread(() -> {
  9. // 可以做其他的事情,不受信号量控制
  10. String name = Thread.currentThread().getName();
  11. try {
  12. s.acquire();
  13. System.out.println(name + " start...");
  14. Thread.sleep(500);
  15. } catch (InterruptedException e) {
  16. e.printStackTrace();
  17. } finally {
  18. s.release();
  19. System.out.println(name + " end...");
  20. }
  21. // 可以做其他的事情,不受信号量控制
  22. }).start();
  23. }
  24. }
  25. }

CountdownLatch

见名知意,倒数计数(而非计时)的门闩,计数结束就开门了,然后可以继续执行指令.
使用场景:用于在一个线程中有些特殊操作,需要在业务逻辑上等待其他几个线程完成特定操作后才能执行.
比线程的join()方法更灵活一些,因为不需要等其他线程全部执行完,而是手动灵活控制.
做个类比:饭店中的传菜员拿来一页订单,客人要求几个菜一起上,传菜员就要等大厨们都做完装盘后才能把才端给客人;而且大厨炒完菜还要刷锅,这时候传菜员是不需要等待的.
看一个例子:

  1. public class T06_TestCountDownLatch {
  2. public static void main(String[] args) {
  3. // 使用线程的join方法
  4. // usingJoin();
  5. // 使用门闩
  6. usingCountDownLatch();
  7. }
  8. private static void usingCountDownLatch() {
  9. Thread[] threads = new Thread[100];
  10. CountDownLatch latch = new CountDownLatch(threads.length);
  11. for(int i=0; i<threads.length; i++) {
  12. threads[i] = new Thread(()->{
  13. int result = 0;
  14. // 业务逻辑a,假设本操作是一些操作的前置条件
  15. for(int j=0; j<10000; j++) {
  16. result += j;
  17. }
  18. // 门闩倒数计数(减1),原子操作
  19. latch.countDown();
  20. // 下面可以有业务逻辑b,c,d无所谓了
  21. try {
  22. TimeUnit.SECONDS.sleep(1);
  23. System.out.println(Thread.currentThread().getName() + "at" + new Date());
  24. } catch (InterruptedException e) {
  25. e.printStackTrace();
  26. }
  27. });
  28. }
  29. for (int i = 0; i < threads.length; i++) {
  30. threads[i].start();
  31. }
  32. try {
  33. // 插上门闩,等倒数计数完了(到0)才能继续执行
  34. latch.await();
  35. } catch (InterruptedException e) {
  36. e.printStackTrace();
  37. }
  38. System.out.println("end latch at" + new Date());
  39. }
  40. // 这个作为对比
  41. private static void usingJoin() {
  42. Thread[] threads = new Thread[100];
  43. for(int i=0; i<threads.length; i++) {
  44. threads[i] = new Thread(()->{
  45. int result = 0;
  46. for(int j=0; j<10000; j++) {
  47. result += j;
  48. }
  49. });
  50. }
  51. for (int i = 0; i < threads.length; i++) {
  52. threads[i].start();
  53. }
  54. // 等threads中的线程全都结束后才能继续执行
  55. for (int i = 0; i < threads.length; i++) {
  56. try {
  57. threads[i].join();
  58. } catch (InterruptedException e) {
  59. e.printStackTrace();
  60. }
  61. }
  62. System.out.println("end join");
  63. }
  64. }

CyclicBarrier

见名知意,循环栅栏.
就像装鸡蛋的托盘一样,鸡蛋把托盘装满后才去装箱.

首先创建一个CyclicBarrier,它规定当一定数量的线程调用CyclicBarrier的await()方法后,CyclicBarrier会执行一些操作(也可以啥都不做);
其他线程调用CyclicBarrier的await()方法后,线程开始阻塞,等待barrier满了(各个线程就绪)后才继续执行.
CyclicBarrier的barrierAction由该批次进入的最后一个线程执行

下面是个例子,可以看出是先Ready后那些线程才能继续执行.

  1. public class T07_TestCyclicBarrier {
  2. public static void main(String[] args) {
  3. // 线程满了后啥都不做,这个应该用不到
  4. //CyclicBarrier barrier = new CyclicBarrier(20);
  5. // lambda表达式,和下面的匿名内部类是等效的
  6. CyclicBarrier barrier = new CyclicBarrier(20, () -> System.out.println("Ready,go! @ " + Instant.now()));
  7. /*CyclicBarrier barrier = new CyclicBarrier(20, new Runnable() {
  8. @Override
  9. public void run() {
  10. System.out.println("Ready,go! @ " + Instant.now());
  11. }
  12. });*/
  13. for (int i = 0; i < 100; i++) {
  14. new Thread(() -> {
  15. try {
  16. TimeUnit.SECONDS.sleep(1);
  17. barrier.await();
  18. System.out.println(Thread.currentThread().getName() + "GO ON @ " + Instant.now());
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. } catch (BrokenBarrierException e) {
  22. e.printStackTrace();
  23. }
  24. }).start();
  25. }
  26. }
  27. }

Phaser(极少用到)

phase:相位,阶段;phaser就是按照不同的相位阶段去执行不同的命令.

有点像是CountdownLatch和CyclicBarrier的结合:先注册一定的数量parties作为参与的线程数,大家都准备就绪后触发一次onAdvance操作;也可以随时修改这个parties数量.
触发onAdvance时可以获取到当前是第几个相位(第几次触发),和当前有多少个注册的parties

可以这么理解:CyclicBarrier是只有一个栅栏,Phaser是纵向好几个栅栏,每个栅栏触发时可以有不同的操作.

关键方法:
0.自定义一个类继承Phaser,并重写onAdvance方法.每次parties到达相位后,会调用onAdvance方法
1.phaser.bulkRegister(int parties)
批量注册parties,有点类似于CountdownLatch的倒数计数的初始化
2.phaser.arriveAndAwaitAdvance()
Arrives at this phaser and awaits others.
各parties准备就绪后到达相位,等待其他parties后才继续执行,注意下一次相位仍会参与
3.phaser.arriveAndDeregister()
Arrives at this phaser and deregisters from it without waiting for others to arrive.
到达这个相位,并且从中注销,不需等其他parties到达就可继续执行,不再参与Phaser规则了

应用场景:
遗传算法(大概是用计算机去模拟达尔文进化策略),这个我没去详细了解.
以简单的结婚流程为例:
1.首先关键人物(比如一对新人,伴郎伴娘和亲朋好友等客人)都要到场;2.然后走个结婚的流程,新人发表感言,客人吃饭,新人敬酒;3.客人陆续离场;4.新人入洞房,相互拥抱.
这些步骤顺序不能乱,行为的执行者也不能乱.(只有在大家离场后,两位新人才能入洞房,哈哈)

代码示例:

  1. public class T09_TestPhaserWedding {
  2. static Random r = new Random();
  3. static MarriagePhaser phaser = new MarriagePhaser();
  4. static void milliSleep(int milli) {
  5. try {
  6. TimeUnit.MILLISECONDS.sleep(milli);
  7. } catch (InterruptedException e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. public static void main(String[] args) {
  12. phaser.bulkRegister(7);
  13. // 5位客人
  14. for(int i=0; i<5; i++) {
  15. new Thread(new Person("p" + i)).start();
  16. }
  17. // 2位新人
  18. new Thread(new Person("bridegroom")).start();
  19. new Thread(new Person("bride")).start();
  20. }
  21. static class MarriagePhaser extends Phaser {
  22. /**
  23. *
  24. * @param phase 当前是第几个阶段.the current phase number on entry to this method,
  25. * before this phaser is advanced
  26. * @param registeredParties 目前多少线程参与.the current number of registered parties
  27. * @return {@code true} if this phaser should terminate
  28. */
  29. @Override
  30. protected boolean onAdvance(int phase, int registeredParties) {
  31. switch (phase) {
  32. case 0:
  33. System.out.println("everyone arrived!" + registeredParties);
  34. System.out.println();
  35. return false;
  36. case 1:
  37. System.out.println("everyone finished eating!" + registeredParties);
  38. System.out.println();
  39. return false;
  40. case 2:
  41. System.out.println("everyone left!" + registeredParties);
  42. System.out.println();
  43. return false;
  44. case 3:
  45. System.out.println("The Newlywed Time!" + registeredParties);
  46. return true;
  47. default:
  48. return true;
  49. }
  50. }
  51. }
  52. static class Person implements Runnable {
  53. String name;
  54. public Person(String name) {
  55. this.name = name;
  56. }
  57. public void arrive() {
  58. milliSleep(r.nextInt(1000));
  59. System.out.printf("%s arrived!\n", name);
  60. phaser.arriveAndAwaitAdvance();
  61. }
  62. public void eat() {
  63. milliSleep(r.nextInt(1000));
  64. System.out.printf("%s finish eating!\n", name);
  65. phaser.arriveAndAwaitAdvance();
  66. }
  67. public void leave() {
  68. milliSleep(r.nextInt(1000));
  69. System.out.printf("%s leave!\n", name);
  70. phaser.arriveAndAwaitAdvance();
  71. // 所有人离去后,保洁人员才打扫座位
  72. System.out.printf("everyone was left, clean %s's seat\n", name);
  73. }
  74. private void hug() {
  75. if(name.equals("bridegroom") || name.equals("bride")) {
  76. milliSleep(r.nextInt(1000));
  77. System.out.printf("%s hug!\n", name);
  78. phaser.arriveAndAwaitAdvance();
  79. } else {
  80. phaser.arriveAndDeregister();
  81. // 注销后,不必等本次相位的所有parties到达,可以随意执行
  82. System.out.printf("%s Deregister\n", name);
  83. }
  84. }
  85. @Override
  86. public void run() {
  87. arrive();
  88. eat();
  89. leave();
  90. hug();
  91. }
  92. }
  93. }

Exchanger交换器

用于两个线程间交换数据.
中间有个类似CyclicBarrier的操作,第一个线程准备好而第二个线程没准备好前,第一个会阻塞;只有两个线程都准备好后才会开始交换,然后继续执行

应用场景:两人做交易(不知道各位有没有玩过零几年很火的游戏<传奇>?)

代码示例:

  1. public class T12_TestExchanger {
  2. static Exchanger<String> exchanger = new Exchanger<>();
  3. public static void main(String[] args) {
  4. new Thread(()->{
  5. String source = "T1";
  6. String getted = null;
  7. try {
  8. TimeUnit.SECONDS.sleep(3);
  9. System.out.println("T1 ready @" + new Date());
  10. getted = exchanger.exchange(source);
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println(Thread.currentThread().getName() + " get:" + getted + "@" + new Date());
  15. }, "t1").start();
  16. new Thread(()->{
  17. String source = "T2";
  18. String getted = null;
  19. try {
  20. System.out.println("T2 ready @" + new Date());
  21. getted = exchanger.exchange(source);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. System.out.println(Thread.currentThread().getName() + " get:" + getted + "@" + new Date());
  26. }, "t2").start();
  27. // 如果再来个T3,程序会一直阻塞下去
  28. /*new Thread(()->{
  29. String source = "T3";
  30. String getted = null;
  31. try {
  32. System.out.println("T3 ready @" + new Date());
  33. getted = exchanger.exchange(source);
  34. } catch (InterruptedException e) {
  35. e.printStackTrace();
  36. }
  37. System.out.println(Thread.currentThread().getName() + " get:" + getted + "@" + new Date());
  38. }, "t3").start();*/
  39. }
  40. }