版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/q258523454/article/details/117998310
假如有3个线程(A,B,C),怎么让它们按照指定的顺序执行任务呢?假设希望按照 A->B->C 的顺序执行。
本文将依次介绍8种方式
目录
4.ReentrantLock-Condition(重入锁)
5.CountDownLatch(减数器)/ CyclicBarrier(栅栏)
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,可以去尝试写一些例子。功能真的很强大!