查看原文
其他

线程顺序执行的 8 种方法,涨姿势了!

俊俊的小熊饼干 脚本之家 2022-04-23

  脚本之家

你与百万开发者在一起

本文经由博客园作者 俊俊的小熊饼干 授权转载

原文地址:https://www.cnblogs.com/wenjunwei/p/10573289.html

如需转载请联系原作者

一.前言

本文使用了8种方法实现在多线程中让线程按顺序运行的方法,涉及到多线程中许多常用的方法,不止为了知道如何让线程按顺序运行,更是让读者对多线程的使用有更深刻的了解。使用的方法如下:

[1] 使用线程的join方法

[2] 使用主线程的join方法

[3] 使用线程的wait方法

[4] 使用线程的线程池方法

[5] 使用线程的Condition(条件变量)方法

[6] 使用线程的CountDownLatch(倒计数)方法

[7] 使用线程的CyclicBarrier(回环栅栏)方法

[8] 使用线程的Semaphore(信号量)方法

二.实现

我们下面需要完成这样一个应用场景:

1.早上;2.测试人员、产品经理、开发人员陆续的来公司上班;3.产品经理规划新需求;4.开发人员开发新需求功能;5.测试人员测试新功能。

规划需求,开发需求新功能,测试新功能是一个有顺序的,我们把thread1看做产品经理,thread2看做开发人员,thread3看做测试人员。

1.使用线程的join方法

join():是Theard的方法,作用是调用线程需等待该join()线程执行完成后,才能继续用下运行。

应用场景:当一个线程必须等待另一个线程执行完毕才能执行时可以使用join方法。

  1. package com.wwj.javabase.thread.order;


  2. /**

  3. * @author wwj

  4. * 通过子程序join使线程按顺序执行

  5. */

  6. public class ThreadJoinDemo {


  7. public static void main(String[] args) {

  8. final Thread thread1 = new Thread(new Runnable() {

  9. @Override

  10. public void run() {

  11. System.out.println("产品经理规划新需求");

  12. }

  13. });


  14. final Thread thread2 = new Thread(new Runnable() {

  15. @Override

  16. public void run() {

  17. try {

  18. thread1.join();

  19. System.out.println("开发人员开发新需求功能");

  20. } catch (InterruptedException e) {

  21. e.printStackTrace();

  22. }

  23. }

  24. });


  25. Thread thread3 = new Thread(new Runnable() {

  26. @Override

  27. public void run() {

  28. try {

  29. thread2.join();

  30. System.out.println("测试人员测试新功能");

  31. } catch (InterruptedException e) {

  32. e.printStackTrace();

  33. }

  34. }

  35. });


  36. System.out.println("早上:");

  37. System.out.println("测试人员来上班了...");

  38. thread3.start();

  39. System.out.println("产品经理来上班了...");

  40. thread1.start();

  41. System.out.println("开发人员来上班了...");

  42. thread2.start();

  43. }

  44. }

运行结果

早上:

测试人员来上班了…

产品经理来上班了…

开发人员来上班了…

产品经理规划新需求开发人员开发新需求功能测试人员测试新功能

2.使用主线程的join方法

这里是在主线程中使用join()来实现对线程的阻塞。

  1. package com.wwj.javabase.thread.order;


  2. /**

  3. * @author wwj

  4. * 通过主程序join使线程按顺序执行

  5. */

  6. public class ThreadMainJoinDemo {


  7. public static void main(String[] args) throws Exception {


  8. final Thread thread1 = new Thread(new Runnable() {

  9. @Override

  10. public void run() {

  11. System.out.println("产品经理正在规划新需求...");

  12. }

  13. });


  14. final Thread thread2 = new Thread(new Runnable() {

  15. @Override

  16. public void run() {

  17. System.out.println("开发人员开发新需求功能");

  18. }

  19. });


  20. final Thread thread3 = new Thread(new Runnable() {

  21. @Override

  22. public void run() {

  23. System.out.println("测试人员测试新功能");

  24. }

  25. });


  26. System.out.println("早上:");

  27. System.out.println("产品经理来上班了");

  28. System.out.println("测试人员来上班了");

  29. System.out.println("开发人员来上班了");

  30. thread1.start();

  31. //在父进程调用子进程的join()方法后,父进程需要等待子进程运行完再继续运行。

  32. System.out.println("开发人员和测试人员休息会...");

  33. thread1.join();

  34. System.out.println("产品经理新需求规划完成!");

  35. thread2.start();

  36. System.out.println("测试人员休息会...");

  37. thread2.join();

  38. thread3.start();

  39. }

  40. }

运行结果

产品经理来上班了

测试人员来上班了

开发人员来上班了

开发人员和测试人员休息会…

产品经理正在规划新需求…

产品经理新需求规划完成!

测试人员休息会…

开发人员开发新需求功能

测试人员测试新功能

3.使用线程的wait方法

wait():是Object的方法作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)

notify()和notifyAll():是Object的方法,作用则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

wait(long timeout):让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。

应用场景:Java实现生产者消费者的方式。

  1. package com.wwj.javabase.thread.order;


  2. /**

  3. * @author wwj

  4. */

  5. public class ThreadWaitDemo {


  6. private static Object myLock1 = new Object();

  7. private static Object myLock2 = new Object();


  8. /**

  9. * 为什么要加这两个标识状态?

  10. * 如果没有状态标识,当t1已经运行完了t2才运行,t2在等待t1唤醒导致t2永远处于等待状态

  11. */

  12. private static Boolean t1Run = false;

  13. private static Boolean t2Run = false;

  14. public static void main(String[] args) {


  15. final Thread thread1 = new Thread(new Runnable() {

  16. @Override

  17. public void run() {

  18. synchronized (myLock1){

  19. System.out.println("产品经理规划新需求...");

  20. t1Run = true;

  21. myLock1.notify();

  22. }

  23. }

  24. });


  25. final Thread thread2 = new Thread(new Runnable() {

  26. @Override

  27. public void run() {

  28. synchronized (myLock1){

  29. try {

  30. if(!t1Run){

  31. System.out.println("开发人员先休息会...");

  32. myLock1.wait();

  33. }

  34. synchronized (myLock2){

  35. System.out.println("开发人员开发新需求功能");

  36. myLock2.notify();

  37. }

  38. } catch (InterruptedException e) {

  39. e.printStackTrace();

  40. }

  41. }

  42. }

  43. });


  44. Thread thread3 = new Thread(new Runnable() {

  45. @Override

  46. public void run() {

  47. synchronized (myLock2){

  48. try {

  49. if(!t2Run){

  50. System.out.println("测试人员先休息会...");

  51. myLock2.wait();

  52. }

  53. System.out.println("测试人员测试新功能");

  54. } catch (InterruptedException e) {

  55. e.printStackTrace();

  56. }

  57. }

  58. }

  59. });


  60. System.out.println("早上:");

  61. System.out.println("测试人员来上班了...");

  62. thread3.start();

  63. System.out.println("产品经理来上班了...");

  64. thread1.start();

  65. System.out.println("开发人员来上班了...");

  66. thread2.start();

  67. }

  68. }

运行结果:这里输出会有很多种顺序,主要是因为线程进入的顺序,造成锁住线程的顺序不一致。

早上:

测试人员来上班了…

产品经理来上班了…

开发人员来上班了…

测试人员先休息会…

产品经理规划新需求…

开发人员开发新需求功能

测试人员测试新功能

4.使用线程的线程池方法

JAVA通过Executors提供了四种线程池

  • 单线程化线程池(newSingleThreadExecutor);

  • 可控最大并发数线程池(newFixedThreadPool);

  • 可回收缓存线程池(newCachedThreadPool);

  • 支持定时与周期性任务的线程池(newScheduledThreadPool)。

单线程化线程池(newSingleThreadExecutor):优点,串行执行所有任务。

submit():提交任务。

shutdown():方法用来关闭线程池,拒绝新任务。

应用场景:串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

  1. package com.wwj.javabase.thread.order;


  2. import java.util.concurrent.ExecutorService;

  3. import java.util.concurrent.Executors;


  4. /**

  5. * @author wwj

  6. * 通过SingleThreadExecutor让线程按顺序执行

  7. */

  8. public class ThreadPoolDemo {


  9. static ExecutorService executorService = Executors.newSingleThreadExecutor();


  10. public static void main(String[] args) throws Exception {


  11. final Thread thread1 = new Thread(new Runnable() {

  12. @Override

  13. public void run() {

  14. System.out.println("产品经理规划新需求");

  15. }

  16. });


  17. final Thread thread2 = new Thread(new Runnable() {

  18. @Override

  19. public void run() {

  20. System.out.println("开发人员开发新需求功能");

  21. }

  22. });


  23. Thread thread3 = new Thread(new Runnable() {

  24. @Override

  25. public void run() {

  26. System.out.println("测试人员测试新功能");

  27. }

  28. });


  29. System.out.println("早上:");

  30. System.out.println("产品经理来上班了");

  31. System.out.println("测试人员来上班了");

  32. System.out.println("开发人员来上班了");

  33. System.out.println("领导吩咐:");

  34. System.out.println("首先,产品经理规划新需求...");

  35. executorService.submit(thread1);

  36. System.out.println("然后,开发人员开发新需求功能...");

  37. executorService.submit(thread2);

  38. System.out.println("最后,测试人员测试新功能...");

  39. executorService.submit(thread3);

  40. executorService.shutdown();

  41. }

  42. }

运行结果

早上:

产品经理来上班了

测试人员来上班了

开发人员来上班了

领导吩咐:

首先,产品经理规划新需求…

然后,开发人员开发新需求功能…

最后,测试人员测试新功能…

产品经理规划新需求开发人员

开发新需求功能

测试人员测试新功能

5.使用线程的Condition(条件变量)方法

Condition(条件变量):通常与一个锁关联。需要在多个Contidion中共享一个锁时,可以传递一个Lock/RLock实例给构造方法,否则它将自己生成一个RLock实例。

  • Condition中await()方法类似于Object类中的wait()方法。

  • Condition中await(long time,TimeUnit unit)方法类似于Object类中的wait(long time)方法。

  • Condition中signal()方法类似于Object类中的notify()方法。

  • Condition中signalAll()方法类似于Object类中的notifyAll()方法。

应用场景:Condition是一个多线程间协调通信的工具类,使得某个,或者某些线程一起等待某个条件(Condition),只有当该条件具备( signal 或者 signalAll方法被调用)时 ,这些等待线程才会被唤醒,从而重新争夺锁。

  1. package com.wwj.javabase.thread.order;


  2. import java.util.concurrent.locks.Condition;

  3. import java.util.concurrent.locks.Lock;

  4. import java.util.concurrent.locks.ReentrantLock;


  5. /**

  6. * @author wwj

  7. * 使用Condition(条件变量)实现线程按顺序运行

  8. */

  9. public class ThreadConditionDemo {


  10. private static Lock lock = new ReentrantLock();

  11. private static Condition condition1 = lock.newCondition();

  12. private static Condition condition2 = lock.newCondition();


  13. /**

  14. * 为什么要加这两个标识状态?

  15. * 如果没有状态标识,当t1已经运行完了t2才运行,t2在等待t1唤醒导致t2永远处于等待状态

  16. */

  17. private static Boolean t1Run = false;

  18. private static Boolean t2Run = false;


  19. public static void main(String[] args) {


  20. final Thread thread1 = new Thread(new Runnable() {

  21. @Override

  22. public void run() {

  23. lock.lock();

  24. System.out.println("产品经理规划新需求");

  25. t1Run = true;

  26. condition1.signal();

  27. lock.unlock();

  28. }

  29. });


  30. final Thread thread2 = new Thread(new Runnable() {

  31. @Override

  32. public void run() {

  33. lock.lock();

  34. try {

  35. if(!t1Run){

  36. System.out.println("开发人员先休息会...");

  37. condition1.await();

  38. }

  39. System.out.println("开发人员开发新需求功能");

  40. t2Run = true;

  41. condition2.signal();

  42. } catch (InterruptedException e) {

  43. e.printStackTrace();

  44. }

  45. lock.unlock();

  46. }

  47. });


  48. Thread thread3 = new Thread(new Runnable() {

  49. @Override

  50. public void run() {

  51. lock.lock();

  52. try {

  53. if(!t2Run){

  54. System.out.println("测试人员先休息会...");

  55. condition2.await();

  56. }

  57. System.out.println("测试人员测试新功能");

  58. lock.unlock();

  59. } catch (InterruptedException e) {

  60. e.printStackTrace();

  61. }

  62. }

  63. });


  64. System.out.println("早上:");

  65. System.out.println("测试人员来上班了...");

  66. thread3.start();

  67. System.out.println("产品经理来上班了...");

  68. thread1.start();

  69. System.out.println("开发人员来上班了...");

  70. thread2.start();

  71. }

  72. }

运行结果:这里输出会有很多种顺序,主要是因为线程进入的顺序,造成锁住线程的顺序不一致

早上:

测试人员来上班了…

产品经理来上班了…

开发人员来上班了…

测试人员先休息会…

产品经理规划新需求

开发人员开发新需求功能

测试人员测试新功能

6.使用线程的CountDownLatch(倒计数)方法

CountDownLatch:位于java.util.concurrent包下,利用它可以实现类似计数器的功能。

关注微信公众号:Java技术栈,在后台回复:多线程,可以获取我整理的 N 篇最新多线程教程,都是干货。

应用场景:比如有一个任务C,它要等待其他任务A,B执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

  1. package com.wwj.javabase.thread.order;


  2. import java.util.concurrent.CountDownLatch;


  3. /**

  4. * @author wwj

  5. * 通过CountDownLatch(倒计数)使线程按顺序执行

  6. */

  7. public class ThreadCountDownLatchDemo {


  8. /**

  9. * 用于判断线程一是否执行,倒计时设置为1,执行后减1

  10. */

  11. private static CountDownLatch c1 = new CountDownLatch(1);


  12. /**

  13. * 用于判断线程二是否执行,倒计时设置为1,执行后减1

  14. */

  15. private static CountDownLatch c2 = new CountDownLatch(1);


  16. public static void main(String[] args) {

  17. final Thread thread1 = new Thread(new Runnable() {

  18. @Override

  19. public void run() {

  20. System.out.println("产品经理规划新需求");

  21. //对c1倒计时-1

  22. c1.countDown();

  23. }

  24. });


  25. final Thread thread2 = new Thread(new Runnable() {

  26. @Override

  27. public void run() {

  28. try {

  29. //等待c1倒计时,计时为0则往下运行

  30. c1.await();

  31. System.out.println("开发人员开发新需求功能");

  32. //对c2倒计时-1

  33. c2.countDown();

  34. } catch (InterruptedException e) {

  35. e.printStackTrace();

  36. }

  37. }

  38. });


  39. Thread thread3 = new Thread(new Runnable() {

  40. @Override

  41. public void run() {

  42. try {

  43. //等待c2倒计时,计时为0则往下运行

  44. c2.await();

  45. System.out.println("测试人员测试新功能");

  46. } catch (InterruptedException e) {

  47. e.printStackTrace();

  48. }

  49. }

  50. });


  51. System.out.println("早上:");

  52. System.out.println("测试人员来上班了...");

  53. thread3.start();

  54. System.out.println("产品经理来上班了...");

  55. thread1.start();

  56. System.out.println("开发人员来上班了...");

  57. thread2.start();

  58. }

  59. }

运行结果

早上:

测试人员来上班了…

产品经理来上班了…

开发人员来上班了…

产品经理规划新需求

开发人员开发新需求功能

测试人员测试新功能

7.使用CyclicBarrier(回环栅栏)实现线程按顺序运行

CyclicBarrier(回环栅栏):通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。

应用场景:公司组织春游,等待所有的员工到达集合地点才能出发,每个人到达后进入barrier状态。都到达后,唤起大家一起出发去旅行。

  1. package com.wwj.javabase.thread.order;


  2. import java.util.concurrent.BrokenBarrierException;

  3. import java.util.concurrent.CyclicBarrier;


  4. /**

  5. * @author wwj

  6. * 使用CyclicBarrier(回环栅栏)实现线程按顺序运行

  7. */

  8. public class CyclicBarrierDemo {


  9. static CyclicBarrier barrier1 = new CyclicBarrier(2);

  10. static CyclicBarrier barrier2 = new CyclicBarrier(2);


  11. public static void main(String[] args) {


  12. final Thread thread1 = new Thread(new Runnable() {

  13. @Override

  14. public void run() {

  15. try {

  16. System.out.println("产品经理规划新需求");

  17. //放开栅栏1

  18. barrier1.await();

  19. } catch (InterruptedException e) {

  20. e.printStackTrace();

  21. } catch (BrokenBarrierException e) {

  22. e.printStackTrace();

  23. }

  24. }

  25. });


  26. final Thread thread2 = new Thread(new Runnable() {

  27. @Override

  28. public void run() {

  29. try {

  30. //放开栅栏1

  31. barrier1.await();

  32. System.out.println("开发人员开发新需求功能");

  33. //放开栅栏2

  34. barrier2.await();

  35. } catch (InterruptedException e) {

  36. e.printStackTrace();

  37. } catch (BrokenBarrierException e) {

  38. e.printStackTrace();

  39. }

  40. }

  41. });


  42. final Thread thread3 = new Thread(new Runnable() {

  43. @Override

  44. public void run() {

  45. try {

  46. //放开栅栏2

  47. barrier2.await();

  48. System.out.println("测试人员测试新功能");

  49. } catch (InterruptedException e) {

  50. e.printStackTrace();

  51. } catch (BrokenBarrierException e) {

  52. e.printStackTrace();

  53. }

  54. }

  55. });


  56. System.out.println("早上:");

  57. System.out.println("测试人员来上班了...");

  58. thread3.start();

  59. System.out.println("产品经理来上班了...");

  60. thread1.start();

  61. System.out.println("开发人员来上班了...");

  62. thread2.start();

  63. }

  64. }

运行结果

早上:

测试人员来上班了…

产品经理来上班了…

开发人员来上班了…

产品经理规划新需求

开发人员开发新需求功能

测试人员测试新功能

8.使用Sephmore(信号量)实现线程按顺序运行

Sephmore(信号量):Semaphore是一个计数信号量,从概念上将,Semaphore包含一组许可证,如果有需要的话,每个acquire()方法都会阻塞,直到获取一个可用的许可证,每个release()方法都会释放持有许可证的线程,并且归还Semaphore一个可用的许可证。然而,实际上并没有真实的许可证对象供线程使用,Semaphore只是对可用的数量进行管理维护。

acquire():当前线程尝试去阻塞的获取1个许可证,此过程是阻塞的,当前线程获取了1个可用的许可证,则会停止等待,继续执行。

release():当前线程释放1个可用的许可证。

应用场景:Semaphore可以用来做流量分流,特别是对公共资源有限的场景,比如数据库连接。假设有这个的需求,读取几万个文件的数据到数据库中,由于文件读取是IO密集型任务,可以启动几十个线程并发读取,但是数据库连接数只有10个,这时就必须控制最多只有10个线程能够拿到数据库连接进行操作。这个时候,就可以使用Semaphore做流量控制。

  1. package com.wwj.javabase.thread.order;


  2. import java.util.concurrent.Semaphore;

  3. /**

  4. * @author wwj

  5. * 使用Sephmore(信号量)实现线程按顺序运行

  6. */

  7. public class SemaphoreDemo {

  8. private static Semaphore semaphore1 = new Semaphore(1);

  9. private static Semaphore semaphore2 = new Semaphore(1);

  10. public static void main(String[] args) {

  11. final Thread thread1 = new Thread(new Runnable() {

  12. @Override

  13. public void run() {

  14. System.out.println("产品经理规划新需求");

  15. semaphore1.release();

  16. }

  17. });


  18. final Thread thread2 = new Thread(new Runnable() {

  19. @Override

  20. public void run() {

  21. try {

  22. semaphore1.acquire();

  23. System.out.println("开发人员开发新需求功能");

  24. semaphore2.release();

  25. } catch (InterruptedException e) {

  26. e.printStackTrace();

  27. }

  28. }

  29. });


  30. Thread thread3 = new Thread(new Runnable() {

  31. @Override

  32. public void run() {

  33. try {

  34. semaphore2.acquire();

  35. thread2.join();

  36. semaphore2.release();

  37. System.out.println("测试人员测试新功能");

  38. } catch (InterruptedException e) {

  39. e.printStackTrace();

  40. }

  41. }

  42. });


  43. System.out.println("早上:");

  44. System.out.println("测试人员来上班了...");

  45. thread3.start();

  46. System.out.println("产品经理来上班了...");

  47. thread1.start();

  48. System.out.println("开发人员来上班了...");

  49. thread2.start();

  50. }

  51. }

运行结果

早上:

测试人员来上班了…

产品经理来上班了…

开发人员来上班了…

产品经理规划新需求

开发人员开发新需求功能

测试人员测试新功能

总结

看完了这么多种方法,是不是对多线程有了更深入的了解呢?不妨自己试试吧(代码拷贝均可运行)

使用的场景还有很多,根据开发需求场景,选择合适的方法,达到事半功倍的效果。

- END -



更多精彩


在公众号后台对话框输入以下关键词

查看更多优质内容!


女朋友 | 大数据 | 运维 | 书单 | 算法

大数据 | JavaScript | Python | 黑客

AI | 人工智能 | 5G | 区块链

机器学习 | 数学 | 送书

●  人人都欠微软一个正版?

●  脚本之家粉丝福利,请查看!

●  程序员怒打产品经理,这个需求做不了

● 致敬经典:Linux/UNIX必读书单推荐给你

 一个故事讲完CPU的工作原理

● 终于有人把 Nginx 说清楚了,图文详解!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存