JUC(14)让线程顺序执行的方法

本文详细介绍了Java中实现线程按顺序执行的多种方法,包括join()、wait()、线程池newSingleThreadExecutor()、Condition、CountDownLatch、CyclicBarrier以及CompletableFuture。每个方法的原理、代码示例和应用场景都有所阐述,展示了在并发编程中如何确保线程执行的顺序性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、可用的几种方法

  1. join()方法,让其他线程加入当前线程中。
  2. wait()方法,让当前线程进入等待状态
  3. 使用线程的线程池方法:newSingleThreadExecutor()
  4. 使用线程的Condition(条件变量)方法
  5. 使用线程的CountDownLatch(倒计数)方法
  6. 使用线程的CyclicBarrier(回环栅栏)方法
  7. 使用线程的Semaphore(信号量)方法

二、实现

2.1 join()方法

  • join():是Theard的方法,作用是阻塞当前线程,直到加入的线程执行完毕,再继续执行当前线程

  • join()方法是Thread中的一个public方法,它有几个重载版本:

    • join()
    • join(long millis) //参数为毫秒
    • join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
  • join()方法实际是利用了wait()方法(wait方法是Object中的),只不过它不用等待notify()/notifyAll(),且不受其影响。

  • 它结束的条件是:等待时间到 或者 目标线程已经run完(通过isAlive()方法来判断)

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

下面大致看看器源码:

public final void join() throws InterruptedException {
    //调用了另外一个有参数的join方法
    join(0);
}
public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    //0则需要一直等到目标线程run完
    if (millis == 0) {
        // 如果被调用join方法的线程是alive状态,则调用join的方法
        while (isAlive()) {
            // == this.wait(0),注意这里释放的是
            //「被调用」join方法的线程对象的锁
            wait(0);
        }
    } else {
         // 如果目标线程未run完且阻塞时间未到,
        //那么调用线程会一直等待。
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            //每次最多等待delay毫秒时间后继续争抢对象锁,获取锁后继续从这里开始的下一行执行,
            //也可能提前被notify() /notifyAll()唤醒,造成delay未一次性消耗完,
            //会继续执行while继续wait(剩下的delay)
            wait(delay);
            // 这个变量now起的不太好,叫elapsedMillis就容易理解了
            now = System.currentTimeMillis() - base;
        }
   }
}

下面我们使用join方法来实现线程的顺序执行:

package com.wlw.ThreadOrder;
//线程的join方法
//join
//执行顺序一直是 t1 t2 t3
public class Test1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            System.out.println("t1-----------");
        });

        Thread t2 = new Thread(()->{
            try {
                t1.join();
                System.out.println("t2-----------");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t3 = new Thread(()->{
            try {
                t2.join();
                System.out.println("t3-----------");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t3.start();
        t1.start();
        t2.start();
    }
}

/*
t1-----------
t2-----------
t3-----------
*/
package com.wlw.ThreadOrder;
//主线程的join方法
public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            System.out.println("t1-----------");
        });

        Thread t2 = new Thread(()->{
            System.out.println("t2-----------");
        });

        Thread t3 = new Thread(()->{
            System.out.println("t3-----------");
        });

        t1.start();
        t1.join();

        t2.start();
        t2.join();

        t3.start();
        t3.join();
        System.out.println("main end");
    }
}
/*
t1-----------
t2-----------
t3-----------
main end
*/

2.2 wait()方法

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

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

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

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

package com.wlw.ThreadOrder;

//保证执行顺序为t1 t2 t3,无论谁先start()
public class Test3 {

    private static Object mylock1 = new Object();
    private static Object mylock2 = new Object();

    //标志位,用来保证线程顺序,让t2始终在t1之后执行,以此类推
    private static Boolean flag1 = false;
    private static Boolean flag2 = false;

    public static void main(String[] args) {

        Thread t1 = new Thread(()->{
            synchronized (mylock1){
                System.out.println("t1..........");
                flag1 = true;
                mylock1.notify();
            }
        });

        Thread t2 = new Thread(()->{
            synchronized (mylock1){
                try {
                    if(!flag1) { //如果这个判断成立,说明t1还未执行
                        System.out.println("t2要在t1之后运行");
                        mylock1.wait();
                    }
                    synchronized (mylock2){
                        System.out.println("t2..........");
                        flag2 = true;
                        mylock2.notify();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread t3 = new Thread(()->{
            synchronized (mylock2){
                try {
                    if(!flag2) { //如果这个判断成立,说明t1还未执行
                        System.out.println("t3要在t2之后运行");
                        mylock2.wait();
                    }
                    System.out.println("t3..........");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        t3.start();
        t2.start();
        t1.start();
    }
}
/*
t3要在t2之后运行
t2要在t1之后运行
t1..........
t2..........
t3..........
*/

2.3 线程池方法newSingleThreadExecutor()

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

  • 单线程化线程池(newSingleThreadExecutor);
  • 可控最大并发数线程池(newFixedThreadPool);
  • 可回收缓存线程池(newCachedThreadPool);
  • 支持定时与周期性任务的线程池(newScheduledThreadPool)。

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

submit():提交任务。

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

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

package com.wlw.ThreadOrder;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

//线程池方法newSingleThreadExecutor()保证线程执行顺序为t1 t2 t3
public class Test4 {

    public static void main(String[] args) {

        ExecutorService threadExecutor = Executors.newSingleThreadExecutor();

        Thread t1 = new Thread(()->{
            System.out.println("t1.........");
        });

        Thread t2 = new Thread(()->{
            System.out.println("t2.........");
        });

        Thread t3 = new Thread(()->{
            System.out.println("t3.........");
        });

 		// 将线程依次加入到线程池中
        threadExecutor.submit(t1);
        threadExecutor.submit(t2);
        threadExecutor.submit(t3);
        // 及时将线程池关闭
        threadExecutor.shutdown();
    }
}
/*
t1.........
t2.........
t3.........
*/

2.4 使用线程的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方法被带调用)时 ,这些等待线程才会被唤醒,从而重新争夺锁。

package com.wlw.ThreadOrder;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//线程的Condition(条件变量)方法保证线程执行顺序为t1 t2 t3
public class Test5 {

    public static int num = 1;
    //num = 1时,执行t1,2时,执行t2,3时,执行t3

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();

        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Condition condition3 = lock.newCondition();

        Thread t1 = new Thread(()->{
            lock.lock();
            try {
                while (num != 1){ //num 不为1 ,则线程t1不执行,陷于等待状态
                    condition1.await();
                }
                System.out.println("t1..........");
                num = 2;
                condition2.signal();//唤醒线程t2
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        });

        Thread t2 = new Thread(()->{
            lock.lock();
            try {
                while (num != 2){ //num 不为2 ,则线程t2不执行,陷于等待状态
                    condition2.await();
                }
                System.out.println("t2..........");
                num = 3;
                condition3.signal();//唤醒线程t3
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        });


        Thread t3 = new Thread(()->{
            lock.lock();
            try {
                while (num != 3){ //num 不为3 ,则线程t3不执行,陷于等待状态
                    condition3.await();
                }
                System.out.println("t3..........");
                num = 1;
                condition1.signal();//唤醒线程t1
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        });

        t2.start();
        t3.start();
        t1.start();
    }
}
/*
t1..........
t2..........
t3..........
*/

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

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

  • CountDownLatch是一种同步辅助,在AQS基础之上实现的一个并发工具类,让我们多个线程执行任务时,需要等待线程执行完成后,才能执行下面的语句,之前线程操作时是使用 Thread.join方法进行等待 。

  • CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。它相当于是一个计数器,这个计数器的初始值就是线程的数量,每当一个任务完成后,计数器的值就会减一,当计数器的值为 0 时,表示所有的线程都已经任务了,然后在 CountDownLatch 上等待的线程就可以恢复执行接下来的任务。

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

package com.wlw.ThreadOrder;

import java.util.concurrent.CountDownLatch;

public class Test6 {

    private static CountDownLatch countDownLatch1 = new CountDownLatch(1);
    private static CountDownLatch countDownLatch2 = new CountDownLatch(1);

    public static void main(String[] args) {

        Thread t1 = new Thread(()->{
            System.out.println("t1.........");
            countDownLatch1.countDown(); //计数器1减1得0,触发(唤醒)计数器1的await()
        });

        Thread t2 = new Thread(()->{
            try {
                countDownLatch1.await();// 等待countDownLatch1归0
                System.out.println("t2.........");
                countDownLatch2.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        Thread t3 = new Thread(()->{
            try {
                countDownLatch2.await();// 等待countDownLatch2归0
                System.out.println("t3.........");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        t3.start();
        t1.start();
        t2.start();
    }
}
/*
t1.........
t2.........
t3.........
*/
import java.util.concurrent.CountDownLatch;

public class ThreadDemo {
    public static void main(String[] args) {

        CountDownLatch countDownLatch1 = new CountDownLatch(0);

        CountDownLatch countDownLatch2 = new CountDownLatch(1);

        CountDownLatch countDownLatch3 = new CountDownLatch(1);


        Thread t1 = new Thread(new Work(countDownLatch1, countDownLatch2),"线程1");
        Thread t2 = new Thread(new Work(countDownLatch2, countDownLatch3),"线程2");
        Thread t3 = new Thread(new Work(countDownLatch3, countDownLatch3),"线程3");

        t1.start();
        t2.start();
        t3.start();
    }

    static class Work implements Runnable {
        CountDownLatch cOne;
        CountDownLatch cTwo;

        public Work(CountDownLatch cOne, CountDownLatch cTwo) {
            super();
            this.cOne = cOne;
            this.cTwo = cTwo;
        }

        @Override
        public void run() {
            try {
                cOne.await();
                System.out.println("执行: " + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                cTwo.countDown();
            }
        }
    }
}

/*
执行: 线程1
执行: 线程2
执行: 线程3
*/

2.6 使用线程的CyclicBarrier(回环栅栏)方法

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

  • 应用场景:集器七颗龙珠召唤神龙,之后七颗龙珠全部齐全之后,才会触发“召唤神龙”。

package com.wlw.ThreadOrder;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
// 使用线程的CyclicBarrier(回环栅栏)方法保证线程执行顺序为t1 t2 t3
public class Test7 {

    private static CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
    private static CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);

    public static void main(String[] args) {

        Thread t1 = new Thread(()->{
            try {
                System.out.println("t1.........");
                cyclicBarrier1.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });

        Thread t2 = new Thread(()->{
            try {
                cyclicBarrier1.await();
                System.out.println("t2.........");
                cyclicBarrier2.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });

        Thread t3 = new Thread(()->{
            try {
                cyclicBarrier2.await();
                System.out.println("t3.........");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        });
        t2.start();
        t3.start();
        t1.start();
    }
}
/*
t1.........
t2.........
t3.........
*/

2.7 使用CompletableFuture来实现多个线程顺序执行。

在Java 8问世前想要实现任务的回调,一般有以下两种方式:

  • 借助Future isDone轮询以判断任务是否执行结束,并获取结果。
  • 借助Guava类库ListenableFuture、FutureCallback。(netty也有类似的实现)

Java 8 CompletableFuture弥补了Java在异步编程方面的弱势。

在Java中异步编程,不一定非要使用rxJava,Java本身的库中的CompletableFuture可以很好的应对大部分的场景。

Java8新增的CompletableFuture则借鉴了Netty等对Future的改造,简化了异步编程的复杂性,并且提供了函数式编程的能力 。

使用Future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isDone()是否为true,这两种方法都不是很好,因为主线程也会被迫等待。

从Java 8开始引入了CompletableFuture,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。

接下来我们就使用CompletableFuture来实现多个线程顺序执行。

import java.util.concurrent.CompletableFuture;
public class ThreadDemo {
    public static void main(String[] args)  {
        Thread t1 = new Thread(new Work(),"线程1");
        Thread t2 = new Thread(new Work(),"线程2");
        Thread t3 = new Thread(new Work(),"线程3");

        CompletableFuture.runAsync(()-> t1.start())
                .thenRun(()->t2.start())
                .thenRun(()->t3.start());
    }

    static class Work implements Runnable{
        @Override
        public void run() {
            System.out.println("执行 : " + Thread.currentThread().getName());
        }
    }
}
/*
运行结果:

执行 : 线程1
执行 : 线程2
执行 : 线程3
*/

三、总结

  • 对于让线程按照顺序执行,最简单的方法还是用join()、线程池的newSingleThreadExecutor()这两个方法,对于用wait()方法:实际上还是生产者消费者案例的思想,代码量较多
  • 其他的方法,如CountDownLatch()、CyclicBarrier(),他们实际上是允许多个线程同时执行的同步器,是属于共享状态的同步器,在这里用它们来实现线程的顺序执行,仅是来做一个验证,它们可以用来实现这个功能,效率并不高,并且在用信号量(Semaphore)来实现时,需要用到join()方法,也可能是我方法不对吧。
  • 综上呢,要实现线程的顺序执行,还是要用join()、线程池的newSingleThreadExecutor()这两个方法最合适。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悬浮海

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值