假如有3个线程(A,B,C),怎么让它们按照指定的顺序执行任务呢?假设希望按照 A->B->C 的顺序执行。
本文将依次介绍8种方式
目录
1.Thread.join()
2.SingleThreadExecutor(单线程池)
3.object.wait/notify(等待通知)
4.ReentrantLock-Condition(重入锁)
5.CountDownLatch(减数器)/ CyclicBarrier(栅栏)
6.Semaphore(信号量)
7.FutureTask
8.猜一猜?
下面的代码可以直接复制,运行。
1.Thread.join()
这种方式不推荐,纯粹的是为了实现这功能。
@SneakyThrows public static void main(String[] args) { // 创建三个线程 A,B,C Thread threadA = new Thread(() -> System.out.println("A"), "A"); Thread threadB = new Thread(() -> System.out.println("B"), "B"); Thread threadC = new Thread(() -> System.out.println("C"), "C"); ? // 使用 Thread.join() 等待线程执行完毕, 这种方式不优雅 threadA.start(); threadA.join(); ? threadB.start(); threadB.join(); ? threadC.start(); threadC.join(); System.exit(0); }
执行结果:
A B C
2.SingleThreadExecutor(单线程池)
用单个线程池来实现顺序执行,这种方式比较简单,就是利用单线程池的FIFO原理。
先定义一个Runnable
@Slf4j
public?class?MyRunnable?implements?Runnable?{
????/**
?????* 自定义线程名
?????*/
????private?String threadName;
????/**
?????* CountDownLatch指令
?????*/
????private?CountDownLatch latch;
????public?MyRunnable(String threadName, CountDownLatch latch)?{
????????this.threadName = threadName;
????????this.latch = latch;
????}
????@Override
????public?void?run()
? ??{
????????try?{
????????????// 等待 countDownLatch.countDown() 命名
????????????latch.await();
????????????System.out.println(threadName);
????????}?catch?(InterruptedException e) {
????????????e.printStackTrace();
????????}
????}
}
创建单个线程池
/**
?* 利用单线程池达到顺序执行
?* {@link?java.util.concurrent.LinkedBlockingQueue} 保证了FIFO顺序
?*/
public?static?void?main(String[] args)?{
????ExecutorService executorService = Executors.newFixedThreadPool(1);
????// CountDownLatch减数机制,countDown触发await,达到同时执行的目的
????CountDownLatch latch =?new?CountDownLatch(1);
????List<String> threadNameList = Arrays.asList("A",?"B",?"C");
????for?(int?i =?0; i <?3; i++) {
????????executorService.execute(new?MyRunnable(threadNameList.get(i), latch));
????}
????latch.countDown();
????executorService.shutdown();
????while?(!executorService.isTerminated()) {
????????// 等待所有线程执行完成
????}
????System.exit(0);
}
执行结果:A->B->C
3.object.wait/notify(等待通知)
不推荐这种方式,原因:不灵活
注意这里有坑,wait()的线程必须要先执行,否则其他线程notify()是无法唤醒的。换句话说,object的这种方式,锁的使用方式一定是先wait()再notify()。
为了简单说明,这里只写A,B两个线程,以A->B顺序执行的示例。
执行代码:
@Slf4j
public?class?Test?{
????/**
?????* 使用 object.wait()/object.notify()
?????*/
????@SneakyThrows
????public?static?void?main(String[] args)?{
????????final?Object pv =?new?Object();
????????ThreadB b =?new?ThreadB();
????????ThreadA a =?new?ThreadA(b);
????????// 期望顺序 A->B
????????ExecutorService executorService = Executors.newFixedThreadPool(10);
????????// 这里必须要让B先执行才能正常执行,否则会让B一直处于wait()
????????// 原因: 如果A先拿到对象锁,执行notify()无法唤醒B, 因为B还没有拿到对象锁,还没有执行wait()
????????// 因此: 同一个锁的执行顺序一定是 wait()-notify()
????????executorService.execute(b);
????????Thread.sleep(100);
????????executorService.execute(a);
????????// 停止接受新任务,当已有任务将执行完,关闭线程池
????????executorService.shutdown();
????????while?(!executorService.isTerminated()) {
????????????// 等待所有线程执行完成
????????}
????????System.exit(0);
????}
????static?class?ThreadB?implements?Runnable?{
????????@Override
????????public?void?run()?{
????????????synchronized?(this) {
????????????????try?{
????????????????????log.info("B线程等待A线程 doing");
????????????????????// wait()的执行前提是当前线程获取了对象控制权,否则会报错:java.lang.IllegalMonitorStateException
????????????????????this.wait();
????????????????????log.info("B线程等待A线程 done");
????????????????}?catch?(InterruptedException e) {
????????????????????e.printStackTrace();
????????????????}
????????????????log.info("B线程执行完成.");
????????????}
????????}
????}
????@AllArgsConstructor
????static?class?ThreadA?implements?Runnable?{
????????private?final?Object obj;
????????@SneakyThrows
????????@Override
????????public?void?run()?{
????????????synchronized?(obj) {
????????????????// notify()不会立马释放对象锁,释放情景: 1.synchronized代码块执行完成; 2.主动释放 wait();
????????????????obj.notify();
????????????????log.info("A线程开始执行.");
????????????????log.info("A线程执行完成.");
????????????}
????????}
????}
}
?执行结果:
B线程等待A线程 doing A线程开始执行. A线程执行完成. B线程等待A线程 done B线程执行完成.
4.ReentrantLock-Condition(重入锁)
这种方式比前一种方式灵活些。原理类似。
实现代码:
@Slf4j
public?class?Test?{
????/**
?????* 重入锁实现(ReentrantLock-Condition)
?????*/
????@SneakyThrows
????public?static?void?main(String[] args)?{
????????ExecutorService executorService = Executors.newFixedThreadPool(3);
????????final?SequenceLock sequenceLock =?new?SequenceLock();
????????Runnable a = () -> sequenceLock.a();
????????Runnable b = () -> sequenceLock.b();
????????Runnable c = () -> sequenceLock.c();
????????executorService.execute(a);
????????executorService.execute(b);
????????executorService.execute(c);
????????Thread.sleep(1000);
????????sequenceLock.getLock().lock();
????????try?{
????????????// 唤醒A线程,依次执行A,B,C
????????????sequenceLock.getConditionA().signal();
????????}?catch?(Exception ex) {
????????????//TODO
????????}?finally?{
????????????sequenceLock.getLock().unlock();
????????}
????????System.exit(0);
????}
????@Data
????static?class?SequenceLock?{
????????private?ReentrantLock lock =?new?ReentrantLock();
????????private?Condition conditionA = lock.newCondition();
????????private?Condition conditionB = lock.newCondition();
????????private?Condition conditionC = lock.newCondition();
????????public?void?a()?{
????????????lock.lock();
????????????try?{
????????????????log.info("A,wait signal");
????????????????// wait()的执行前提是当前线程获取了对象控制权,否则会报错:java.lang.IllegalMonitorStateException
????????????????conditionA.await();
????????????????log.info("A");
????????????????// 同 notify()一样,并不会立马释放锁,等程序全部执行完,直到 unlock
????????????????conditionB.signal();
????????????}?catch?(Exception e) {
????????????????e.printStackTrace();
????????????}?finally?{
????????????????lock.unlock();
????????????}
????????}
????????public?void?b()?{
????????????lock.lock();
????????????try?{
????????????????log.info("B,wait signal");
????????????????conditionB.await();
????????????????log.info("B");
????????????????conditionC.signal();
????????????}?catch?(Exception e) {
????????????????e.printStackTrace();
????????????}?finally?{
????????????????lock.unlock();
????????????}
????????}
????????public?void?c()?{
????????lock.lock();
????????????try?{
????????????????log.info("C,wait signal");
????????????????conditionC.await();
????????????????log.info("C");
????????????}?catch?(Exception e) {
????????????????e.printStackTrace();
????????????}?finally?{
????????????????lock.unlock();
????????????}
????????}
????}
}
执行结果:
A,wait signal B,wait signal C,wait signal A B C
5.CountDownLatch(减数器)/ CyclicBarrier(栅栏)
利用CountDownLatch、CyclicBarrier 当成信号变量,作顺序开关。这里只写CountDownLatch代码,CyclicBarrier是类似的。注意:这里需要两个信号通知,因此需要定义两个CountDownLatch。
代码如下:
@Slf4j
public?class?Test?{
????/**
?????* CountDownLatch(减数器)实现/ CyclicBarrier(栅栏)实现
?????* {@link?CountDownLatch}
?????* {@link?CyclicBarrier}
?????*/
????public?static?void?main(String[] args)?{
????????// 3个线程需要2两个信号通知,A->B需要一个,B->C需要一个
????????CountDownLatch signalAB =?new?CountDownLatch(1);
????????CountDownLatch signalBC =?new?CountDownLatch(1);
????????A a =?new?A(signalAB);
????????B b =?new?B(signalAB,signalBC);
????????C c =?new?C(signalBC);
????????// 线程池中 按顺序执行 A->B->C
????????ExecutorService executorService = Executors.newFixedThreadPool(10);
????????executorService.execute(b);
????????executorService.execute(c);
????????executorService.execute(a);
????????// 停止接受新任务,当已有任务将执行完,关闭线程池
????????executorService.shutdown();
????????while?(!executorService.isTerminated()) {
????????????// 等待所有线程执行完成
????????}
????????System.out.println("over");
????????System.exit(0);
????}
????@AllArgsConstructor
????static?class?A?implements?Runnable?{
????????private?CountDownLatch latchAB;
????????@SneakyThrows
????????@Override
????????public?void?run()?{
????????????log.info("A");
????????????latchAB.countDown();
????????}
????}
????@AllArgsConstructor
????static?class?B?implements?Runnable?{
????????private?CountDownLatch latchAB;
????????private?CountDownLatch latchBC;
????????@SneakyThrows
????????@Override
????????public?void?run()?{
????????????latchAB.await();
????????????log.info("B");
????????????latchBC.countDown();
????????}
????}
????@AllArgsConstructor
????static?class?C?implements?Runnable?{
????????private?CountDownLatch latchBC;
????????@SneakyThrows
????????@Override
????????public?void?run()?{
????????????latchBC.await();
????????????log.info("C");
????????}
????}
}
执行结果:
A->B->C
6.Semaphore(信号量)
其实跟CountDownLatch 一样,都是作为信号量来传递。同样定义两个信号量。执行结果仍然是A->B->C。
代码如下:
@Slf4j
public?class?Test?{
????/**
?????* Semaphore 信号量实现
?????* {@link?Semaphore}
?????*/
????public?static?void?main(String[] args)?{
????????// 3个线程需要2两个信号量,A->B需要一个,B->C需要一个
????????Semaphore signalAB =?new?Semaphore(0);
????????Semaphore signalBC =?new?Semaphore(0);
????????A a =?new?A(signalAB);
????????B b =?new?B(signalAB, signalBC);
????????C c =?new?C(signalBC);
????????// 线程池中 按顺序执行 A->B->C
????????ExecutorService executorService = Executors.newFixedThreadPool(10);
????????executorService.execute(b);
????????executorService.execute(c);
????????executorService.execute(a);
????????// 停止接受新任务,当已有任务将执行完,关闭线程池
????????executorService.shutdown();
????????while?(!executorService.isTerminated()) {
????????????// 等待所有线程执行完成
????????}
????????System.out.println("over");
????????System.exit(0);
????}
????@AllArgsConstructor
????static?class?A?implements?Runnable?{
????????private?Semaphore semaphoreAB;
????????@SneakyThrows
????????@Override
????????public?void?run()?{
????????????log.info("A");
????????????semaphoreAB.release();
????????}
????}
????@AllArgsConstructor
????static?class?B?implements?Runnable?{
????????private?Semaphore semaphoreAB;
????????private?Semaphore semaphoreBC;
????????@SneakyThrows
????????@Override
????????public?void?run()?{
????????????// semaphore 信号量-1,总数为0的时候会等待
????????????semaphoreAB.acquire();
????????????log.info("B");
????????????// semaphore 信号量+1
????????????semaphoreBC.release();
????????}
????}
????@AllArgsConstructor
????static?class?C?implements?Runnable?{
????????private?Semaphore semaphoreBC;
????????@SneakyThrows
????????@Override
????????public?void?run()?{
????????????semaphoreBC.acquire();
????????????log.info("C");
????????}
????}
}
7.FutureTask
通过FutureTask的阻塞特性直接实现线程的顺序执行,这种方式比较简单,有点类似于单线程池执行。执行结果仍然是A->B->C。
代码如下:
@Slf4j
public?class?ThreadABC?{
????/**
?????* FutureTask 阻塞特性实现
?????*/
????public?static?void?main(String[] args)?throws?ExecutionException, InterruptedException?{
????????ExecutorService executorService = Executors.newFixedThreadPool(3);
????????// 实际上 ExecutorService.submit 还是用的 FutureTask
????????Object a = executorService.submit(new?Runnable() {
????????????@SneakyThrows
????????????@Override
????????????public?void?run()?{
????????????????log.info("A");
????????????}
????????}).get();
????????Object b = executorService.submit(new?Runnable() {
????????????@SneakyThrows
????????????@Override
????????????public?void?run()?{
????????????????log.info("B");
????????????}
????????}).get();
????????Object c = executorService.submit(new?Runnable() {
????????????@SneakyThrows
????????????@Override
????????????public?void?run()?{
????????????????log.info("C");
????????????}
????????}).get();
????????log.info("main thread done.");
????????System.exit(0);
????}
}
8.CompletableFuture (推荐)
JDK1.8中 CompletableFuture提供了非常强大的Future的扩展功能,简化异步编程。提供函数式编程的能力,可帮助我们完成复杂的线程的阶段行编程(CompletionStage)。具体这里不详细介绍,不了解的朋友可以去网上查一下资料。
执行代码:
@Slf4j
public?class?ThreadABC?{
????/**
?????* CompletableFuture (推荐)
?????* JDK1.8中 CompletableFuture提供了非常强大的Future的扩展功能,简化异步编程,
?????* 提供函数式编程的能力,可帮助我们完成复杂的线程的阶段行编程(CompletionStage)
?????* {@link?java.util.concurrent.CompletableFuture}
?????*/
????public?static?void?main(String[] args)?throws?InterruptedException?{
????????ExecutorService executorService = Executors.newFixedThreadPool(3);
????????// 有a,b,c三个线程(任务)
????????Runnable a = () -> log.info("A");
????????Runnable b = () -> log.info("B");
????????Runnable c = () -> log.info("C");
????????// 异步执行
????????CompletableFuture.runAsync(a, executorService).thenRun(b).thenRun(c);
????????log.info("main thread.");
????????// 停止接受新任务,当已有任务将执行完,关闭线程池
????????executorService.shutdown();
????????while?(!executorService.isTerminated()) {
????????????// 等待所有线程执行完成
????????}
????????System.exit(0);
????}
}
执行结果:
main thread. A B C
多线程的执行顺序,在面试过程中经常会被问到。不了解的,可以收藏一下。笔者建议不了解CompletableFuture,可以去尝试写一些例子。功能真的很强大!
|