• 关于线程的执行顺序


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

    [1] 使用线程的join方法

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

    [3] 使用线程的wait方法

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

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

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

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

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

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

    1.早上;2.测试人员、产品经理、开发人员陆续的来公司上班;3.产品经理规划新需求;4.开发人员开发新需求功能;5.测试人员测试新功能。 规划需求,开发需求新功能,测试新功能是一个有顺序的,我们把thread1看做产品经理,thread2看做开发人员,thread3看做测试人员。

    (一)、使用线程的 join 方法

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

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

     // 通过子程序join使线程按顺序执行

    1
    public static void main(String[] args) { 2 final Thread thread1 = new Thread(new Runnable() { 3 @Override 4 public void run() { 5 System.out.println("(5).产品经理规划新需求"); 6 } 7 }); 8 9 final Thread thread2 = new Thread(new Runnable() { 10 @Override 11 public void run() { 12 try { 13 thread1.join(); 14 System.out.println("(6).开发人员开发新需求功能"); 15 } catch (InterruptedException e) { 16 e.printStackTrace(); 17 } 18 } 19 }); 20 21 Thread thread3 = new Thread(new Runnable() { 22 @Override 23 public void run() { 24 try { 25 thread2.join(); 26 System.out.println("(7).测试人员测试新功能"); 27 } catch (InterruptedException e) { 28 e.printStackTrace(); 29 } 30 } 31 }); 32 33 System.out.println("(1).早上:"); 34 System.out.println("(2).测试人员来上班了..."); 35 thread3.start(); 36 System.out.println("(3).产品经理来上班了..."); 37 thread1.start(); 38 System.out.println("(4).开发人员来上班了..."); 39 thread2.start(); 40 }

    执行结果:

    (1).早上:
    (2).测试人员来上班了...
    (3).产品经理来上班了...
    (4).开发人员来上班了...
    (5).产品经理规划新需求
    (6).开发人员开发新需求功能
    (7).测试人员测试新功能
    

    (二)、使用主线程的 join 方法

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

     // 通过主程序join使线程按顺序执行 

    1
    public static void main(String[] args) throws Exception { 2 3 final Thread thread1 = new Thread(new Runnable() { 4 @Override 5 public void run() { 6 System.out.println("(6).产品经理正在规划新需求..."); 7 } 8 }); 9 10 final Thread thread2 = new Thread(new Runnable() { 11 @Override 12 public void run() { 13 System.out.println("(9).开发人员开发新需求功能"); 14 } 15 }); 16 17 final Thread thread3 = new Thread(new Runnable() { 18 @Override 19 public void run() { 20 System.out.println("(10).测试人员测试新功能"); 21 } 22 }); 23 24 System.out.println("(1).早上:"); 25 System.out.println("(2).产品经理来上班了"); 26 System.out.println("(3).测试人员来上班了"); 27 System.out.println("(4).开发人员来上班了"); 28 thread1.start(); 29 // 在父进程调用子进程的join()方法后,父进程需要等待子进程运行完再继续运行。 30 System.out.println("(5).开发人员和测试人员休息会..."); 31 thread1.join(); 32 System.out.println("(7).产品经理新需求规划完成!"); 33 thread2.start(); 34 System.out.println("(8).测试人员休息会..."); 35 thread2.join(); 36 thread3.start(); 37 }

    执行结果:

    (1).早上:
    (2).产品经理来上班了
    (3).测试人员来上班了
    (4).开发人员来上班了
    (5).开发人员和测试人员休息会...
    (6).产品经理正在规划新需求...
    (7).产品经理新需求规划完成!
    (8).测试人员休息会...
    (9).开发人员开发新需求功能
    (10).测试人员测试新功能


    (三)、使用线程的 wait 方法

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

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

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

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

     1 /**
     2  * 
     3  * @ClassName:  TheardTest   
     4  * @Description:TODO 使用线程的 wait 方法
     5  * @author: Hujh
     6  * @date: 2019年11月29日 下午3:53:50
     7  */
     8 public class TheardTest {
     9         private static Object myLock1 = new Object();
    10         private static Object myLock2 = new Object();
    11 
    12         /**
    13          * 为什么要加这两个标识状态?
    14          * 如果没有状态标识,当t1已经运行完了t2才运行,t2在等待t1唤醒导致t2永远处于等待状态
    15          */
    16         private static Boolean t1Run = false;
    17         private static Boolean t2Run = false;
    18         
    19         public static void main(String[] args) {
    20 
    21             final Thread thread1 = new Thread(new Runnable() {
    22                 @Override
    23                 public void run() {
    24                     synchronized (myLock1){
    25                         System.out.println("(6).产品经理规划新需求...");
    26                         t1Run = true;
    27                         myLock1.notify();
    28                     }
    29                 }
    30             });
    31 
    32             final Thread thread2 = new Thread(new Runnable() {
    33                 @Override
    34                 public void run() {
    35                     synchronized (myLock1){
    36                         try {
    37                             if(!t1Run){
    38                                 System.out.println("开发人员先休息会...");
    39                                 myLock1.wait();
    40                             }
    41                             synchronized (myLock2){
    42                                 System.out.println("(7).开发人员开发新需求功能");
    43                                 // 唤醒等待线程
    44                                 myLock2.notify();
    45                             }
    46                         } catch (InterruptedException e) {
    47                             e.printStackTrace();
    48                         }
    49                     }
    50                 }
    51             });
    52 
    53             Thread thread3 = new Thread(new Runnable() {
    54                 @Override
    55                 public void run() {
    56                     synchronized (myLock2){
    57                         try {
    58                             if(!t2Run){
    59                                 System.out.println("(5).测试人员先休息会...");
    60                                 // 进入等待...
    61                                 myLock2.wait();
    62                                 
    63                                 // 如果此线程得不到释放,2秒后自动释放。
    64                                 // myLock2.wait(2000);
    65                             }
    66                             System.out.println("(8).测试人员测试新功能");
    67                         } catch (InterruptedException e) {
    68                             e.printStackTrace();
    69                         }
    70                     }
    71                 }
    72             });
    73 
    74             System.out.println("(1).早上:");
    75             System.out.println("(2).测试人员来上班了...");
    76             thread3.start();
    77             System.out.println("(3).产品经理来上班了...");
    78             thread1.start();
    79             System.out.println("(4).开发人员来上班了...");
    80             thread2.start();
    81     }
    82 }

    执行结果:

    (1).早上:
    (2).测试人员来上班了...
    (3).产品经理来上班了...
    (4).开发人员来上班了...
    (5).测试人员先休息会...
    (6).产品经理规划新需求...
    (7).开发人员开发新需求功能
    (8).测试人员测试新功能
    

     
    (四)、使用线程的线程池方法

    JAVA通过Executors提供了四种线程池:
      (1)单线程化线程池(newSingleThreadExecutor);
      (2)可控最大并发数线程池(newFixedThreadPool);
      (3)可回收缓存线程池(newCachedThreadPool);
      (4)支持定时与周期性任务的线程池(newScheduledThreadPool)。
    单线程化线程池(newSingleThreadExecutor):优点,串行执行所有任务。
      submit():提交任务。
      shutdown():方法用来关闭线程池,拒绝新任务。
    应用场景:串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

     1 import java.util.concurrent.ExecutorService;
     2 import java.util.concurrent.Executors;
     3 
     4 /**
     5  * 
     6  * @ClassName:  TheardTest   
     7  * @Description:TODO  通过SingleThreadExecutor让线程按顺序执行
     8  * @author: Hujh
     9  * @date: 2019年11月29日 下午3:53:50
    10  */
    11 public class TheardTest {
    12         
    13     static ExecutorService executorService = Executors.newSingleThreadExecutor();
    14 
    15     public static void main(String[] args) throws Exception {
    16 
    17         final Thread thread1 = new Thread(new Runnable() {
    18             @Override
    19             public void run() {
    20                 System.out.println("(9).产品经理规划新需求");
    21             }
    22         });
    23 
    24         final Thread thread2 = new Thread(new Runnable() {
    25             @Override
    26             public void run() {
    27                 System.out.println("(10).开发人员开发新需求功能");
    28             }
    29         });
    30 
    31         Thread thread3 = new Thread(new Runnable() {
    32             @Override
    33             public void run() {
    34                 System.out.println("(11).测试人员测试新功能");
    35             }
    36         });
    37 
    38         System.out.println("(1).早上:");
    39         System.out.println("(2).产品经理来上班了");
    40         System.out.println("(3).测试人员来上班了");
    41         System.out.println("(4).开发人员来上班了");
    42         System.out.println("(5).领导吩咐:");
    43         System.out.println("(6).首先,产品经理规划新需求...");
    44         executorService.submit(thread1);
    45         System.out.println("(7).然后,开发人员开发新需求功能...");
    46         executorService.submit(thread2);
    47         System.out.println("(8).最后,测试人员测试新功能...");
    48         executorService.submit(thread3);
    49         executorService.shutdown();
    50     }
    51 }

    执行结果:

    (1).早上:
    (2).产品经理来上班了
    (3).测试人员来上班了
    (4).开发人员来上班了
    (5).领导吩咐:
    (6).首先,产品经理规划新需求...
    (7).然后,开发人员开发新需求功能...
    (8).最后,测试人员测试新功能...
    (9).产品经理规划新需求
    (10).开发人员开发新需求功能
    (11).测试人员测试新功能
    

    (五)、使用线程的 Condition(条件变量) 方法

    Condition(条件变量):通常与一个锁关联。需要在多个Contidion中共享一个锁时,可以传递一个Lock/RLock实例给构造方法,否则它将自己生成一个RLock实例。
      (1)Condition中await()方法类似于Object类中的wait()方法。
      (2)Condition中await(long time,TimeUnit unit)方法类似于Object类中的wait(long time)方法。
      (3)Condition中signal()方法类似于Object类中的notify()方法。
      (4)Condition中signalAll()方法类似于Object类中的notifyAll()方法。


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

     1 import java.util.concurrent.locks.Condition;
     2 import java.util.concurrent.locks.Lock;
     3 import java.util.concurrent.locks.ReentrantLock;
     4 
     5 /**
     6  * 
     7  * @ClassName:  TheardTest   
     8  * @Description:TODO  使用线程的 Condition(条件变量) 方法
     9  * @author: Hujh
    10  * @date: 2019年11月29日 下午3:53:50
    11  */
    12 public class TheardTest {
    13         
    14     private static Lock lock = new ReentrantLock();
    15     private static Condition condition1 = lock.newCondition();
    16     private static Condition condition2 = lock.newCondition();
    17 
    18     /**
    19      * 为什么要加这两个标识状态?
    20      * 如果没有状态标识,当t1已经运行完了t2才运行,t2在等待t1唤醒导致t2永远处于等待状态
    21      */
    22     private static Boolean t1Run = false;
    23     private static Boolean t2Run = false;
    24 
    25     public static void main(String[] args) {
    26 
    27         final Thread thread1 = new Thread(new Runnable() {
    28             @Override
    29             public void run() {
    30                 lock.lock();
    31                 System.out.println("(6).产品经理规划新需求");
    32                 t1Run = true;
    33                 condition1.signal();
    34                 lock.unlock();
    35             }
    36         });
    37 
    38         final Thread thread2 = new Thread(new Runnable() {
    39             @Override
    40             public void run() {
    41                 lock.lock();
    42                 try {
    43                     if(!t1Run){
    44                         System.out.println("开发人员先休息会...");
    45                         condition1.await();
    46                     }
    47                     System.out.println("(7).开发人员开发新需求功能");
    48                     t2Run = true;
    49                     condition2.signal();
    50                 } catch (InterruptedException e) {
    51                     e.printStackTrace();
    52                 }
    53                 lock.unlock();
    54             }
    55         });
    56 
    57         Thread thread3 = new Thread(new Runnable() {
    58             @Override
    59             public void run() {
    60                 lock.lock();
    61                 try {
    62                     if(!t2Run){
    63                         System.out.println("(5).测试人员先休息会...");
    64                         condition2.await();
    65                     }
    66                     System.out.println("(8).测试人员测试新功能");
    67                     lock.unlock();
    68                 } catch (InterruptedException e) {
    69                     e.printStackTrace();
    70                 }
    71             }
    72         });
    73 
    74         System.out.println("(1).早上:");
    75         System.out.println("(2).测试人员来上班了...");
    76         thread3.start();
    77         System.out.println("(3).产品经理来上班了...");
    78         thread1.start();
    79         System.out.println("(4).开发人员来上班了...");
    80         thread2.start();
    81     }
    82 }

    执行结果:

    (1).早上:
    (2).测试人员来上班了...
    (3).产品经理来上班了...
    (4).开发人员来上班了...
    (5).测试人员先休息会...
    (6).产品经理规划新需求
    (7).开发人员开发新需求功能
    (8).测试人员测试新功能
    

    (六)、使用线程的 CuDownLatch(倒计数) 方法

    CountDownLatch:位于java.util.concurrent包下,利用它可以实现类似计数器的功能。
    应用场景:比如有一个任务C,它要等待其他任务A,B执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

     1 import java.util.concurrent.CountDownLatch;
     2 
     3 /**
     4  * 
     5  * @ClassName:  TheardTest   
     6  * @Description:TODO  使用线程的 CuDownLatch(倒计数) 方法
     7  * @author: Hujh
     8  * @date: 2019年11月29日 下午3:53:50
     9  */
    10 public class TheardTest {
    11     /**
    12      * 用于判断线程一是否执行,倒计时设置为1,执行后减1
    13      */
    14     private static CountDownLatch c1 = new CountDownLatch(1);
    15 
    16     /**
    17      * 用于判断线程二是否执行,倒计时设置为1,执行后减1
    18      */
    19     private static CountDownLatch c2 = new CountDownLatch(1);
    20 
    21     public static void main(String[] args) {
    22         final Thread thread1 = new Thread(new Runnable() {
    23             @Override
    24             public void run() {
    25                 System.out.println("(5).产品经理规划新需求");
    26                 // 对c1倒计时-1
    27                 c1.countDown();
    28             }
    29         });
    30 
    31         final Thread thread2 = new Thread(new Runnable() {
    32             @Override
    33             public void run() {
    34                 try {
    35                     // 等待c1倒计时,计时为0则往下运行
    36                     c1.await();
    37                     System.out.println("(6).开发人员开发新需求功能");
    38                     // 对c2倒计时-1
    39                     c2.countDown();
    40                 } catch (InterruptedException e) {
    41                     e.printStackTrace();
    42                 }
    43             }
    44         });
    45 
    46         Thread thread3 = new Thread(new Runnable() {
    47             @Override
    48             public void run() {
    49                 try {
    50                     // 等待c2倒计时,计时为0则往下运行
    51                     c2.await();
    52                     System.out.println("(7).测试人员测试新功能");
    53                 } catch (InterruptedException e) {
    54                     e.printStackTrace();
    55                 }
    56             }
    57         });
    58 
    59         System.out.println("(1).早上:");
    60         System.out.println("(2).测试人员来上班了...");
    61         thread3.start();
    62         System.out.println("(3).产品经理来上班了...");
    63         thread1.start();
    64         System.out.println("(4).开发人员来上班了...");
    65         thread2.start();
    66     }
    67 }

    执行结果:

    (1).早上:
    (2).测试人员来上班了...
    (3).产品经理来上班了...
    (4).开发人员来上班了...
    (5).产品经理规划新需求
    (6).开发人员开发新需求功能
    (7).测试人员测试新功能
    

     (七)、使用 CyclicBarrier (回环栅栏)实现线程按顺序执行

    CyclicBarrier(回环栅栏):通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。
    应用场景:公司组织春游,等待所有的员工到达集合地点才能出发,每个人到达后进入barrier状态。都到达后,唤起大家一起出发去旅行。

     1 import java.util.concurrent.BrokenBarrierException;
     2 import java.util.concurrent.CyclicBarrier;
     3 
     4 /**
     5  * 
     6  * @ClassName:  TheardTest   
     7  * @Description:TODO  使用 CyclicBarrier (回环栅栏)实现线程按顺序执行
     8  * @author: Hujh
     9  * @date: 2019年11月29日 下午3:53:50
    10  */
    11 public class TheardTest {
    12     
    13     static CyclicBarrier barrier1 = new CyclicBarrier(2);
    14     static CyclicBarrier barrier2 = new CyclicBarrier(2);
    15 
    16     public static void main(String[] args) {
    17 
    18         final Thread thread1 = new Thread(new Runnable() {
    19             @Override
    20             public void run() {
    21                 try {
    22                     System.out.println("(5).产品经理规划新需求");
    23                     // 放开栅栏1
    24                     barrier1.await();
    25                 } catch (InterruptedException e) {
    26                     e.printStackTrace();
    27                 } catch (BrokenBarrierException e) {
    28                     e.printStackTrace();
    29                 }
    30             }
    31         });
    32 
    33         final Thread thread2 = new Thread(new Runnable() {
    34             @Override
    35             public void run() {
    36                 try {
    37                     // 放开栅栏1
    38                     barrier1.await();
    39                     System.out.println("(6).开发人员开发新需求功能");
    40                     // 放开栅栏2
    41                     barrier2.await();
    42                 } catch (InterruptedException e) {
    43                     e.printStackTrace();
    44                 } catch (BrokenBarrierException e) {
    45                     e.printStackTrace();
    46                 }
    47             }
    48         });
    49 
    50         final Thread thread3 = new Thread(new Runnable() {
    51             @Override
    52             public void run() {
    53                 try {
    54                     // 放开栅栏2
    55                     barrier2.await();
    56                     System.out.println("(7).测试人员测试新功能");
    57                 } catch (InterruptedException e) {
    58                     e.printStackTrace();
    59                 } catch (BrokenBarrierException e) {
    60                     e.printStackTrace();
    61                 }
    62             }
    63         });
    64 
    65         System.out.println("(1).早上:");
    66         System.out.println("(2).测试人员来上班了...");
    67         thread3.start();
    68         System.out.println("(3).产品经理来上班了...");
    69         thread1.start();
    70         System.out.println("(4).开发人员来上班了...");
    71         thread2.start();
    72     }
    73 }

    执行结果:

    (1).早上:
    (2).测试人员来上班了...
    (3).产品经理来上班了...
    (4).开发人员来上班了...
    (5).产品经理规划新需求
    (6).开发人员开发新需求功能
    (7).测试人员测试新功能
    

    (八)、使用线程的 Sephmore(信号量) 实现线程按顺序执行

    Sephmore(信号量):Semaphore是一个计数信号量,从概念上将,Semaphore包含一组许可证,如果有需要的话,每个acquire()方法都会阻塞,直到获取一个可用的许可证,每个release()方法都会释放持有许可证的线程,并且归还Semaphore一个可用的许可证。然而,实际上并没有真实的许可证对象供线程使用,Semaphore只是对可用的数量进行管理维护。
    acquire():当前线程尝试去阻塞的获取1个许可证,此过程是阻塞的,当前线程获取了1个可用的许可证,则会停止等待,继续执行。
    release():当前线程释放1个可用的许可证。
    应用场景:Semaphore可以用来做流量分流,特别是对公共资源有限的场景,比如数据库连接。假设有这个的需求,读取几万个文件的数据到数据库中,由于文件读取是IO密集型任务,可以启动几十个线程并发读取,但是数据库连接数只有10个,这时就必须控制最多只有10个线程能够拿到数据库连接进行操作。这个时候,就可以使用Semaphore做流量控制。

     1 import java.util.concurrent.Semaphore;
     2 
     3 /**
     4  * 
     5  * @ClassName: TheardTest
     6  * @Description:TODO 使用线程的 Sephmore(信号量) 实现线程按顺序执行
     7  * @author: Hujh
     8  * @date: 2019年11月29日 下午3:53:50
     9  */
    10 public class TheardTest {
    11     
    12     private static Semaphore semaphore1 = new Semaphore(1);
    13     private static Semaphore semaphore2 = new Semaphore(1);
    14 
    15     public static void main(String[] args) {
    16         final Thread thread1 = new Thread(new Runnable() {
    17             @Override
    18             public void run() {
    19                 System.out.println("(5).产品经理规划新需求");
    20                 semaphore1.release();
    21             }
    22         });
    23 
    24         final Thread thread2 = new Thread(new Runnable() {
    25             @Override
    26             public void run() {
    27                 try {
    28                     semaphore1.acquire();
    29                     System.out.println("(6).开发人员开发新需求功能");
    30                     semaphore2.release();
    31                 } catch (InterruptedException e) {
    32                     e.printStackTrace();
    33                 }
    34             }
    35         });
    36 
    37         Thread thread3 = new Thread(new Runnable() {
    38             @Override
    39             public void run() {
    40                 try {
    41                     semaphore2.acquire();
    42                     thread2.join();
    43                     semaphore2.release();
    44                     System.out.println("(7).测试人员测试新功能");
    45                 } catch (InterruptedException e) {
    46                     e.printStackTrace();
    47                 }
    48             }
    49         });
    50 
    51         System.out.println("(1).早上:");
    52         System.out.println("(2).测试人员来上班了...");
    53         thread3.start();
    54         System.out.println("(3).产品经理来上班了...");
    55         thread1.start();
    56         System.out.println("(4).开发人员来上班了...");
    57         thread2.start();
    58     }
    59 }

    执行结果:

    (1).早上:
    (2).测试人员来上班了...
    (3).产品经理来上班了...
    (4).开发人员来上班了...
    (5).产品经理规划新需求
    (6).开发人员开发新需求功能
    (7).测试人员测试新功能
    

    总结:

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

  • 相关阅读:
    一分钟认识:Cucumber框架(一)
    迭代=冲刺?
    Nresource服务之接口缓存化
    58集团支付网关设计
    服务治理在资源中心的实践
    资源中心——连接池调优
    4种常用的演讲结构: 黄金圈法则结构、PREP结构、时间轴结构、金字塔结构
    微服务时代,领域驱动设计在携程国际火车票的实践
    Sentinel -- FLOW SLOT核心原理篇
    管理篇-如何跨部门沟通?
  • 原文地址:https://www.cnblogs.com/hujunhui/p/11958427.html
Copyright © 2020-2023  润新知