1、通过join()的方式
子线程调用join()的时候,主线程等待子线程执行完再执行。如果让多个线程顺序执行的话,那么需要他们按顺序调用start()。
java
/**
* - 第一个迭代(i=0):
* 启动线程t1 -> 然后调用t1.join()。
* 主线程(执行testMethod2的线程)会在t1.join()处阻塞,直到t1线程执行完毕。
* - 第二个迭代(i=1):
* 只有等到t1执行完毕,主线程才会继续执行,然后启动线程t2,并调用t2.join(),主线程等待t2执行完毕。
* - 第三个迭代(i=2):
* 同样,主线程等待t2执行完毕后,启动t3,然后等待t3执行完毕。
* @throws InterruptedException
*/
@Test
void testMethod1() throws InterruptedException {
List<Thread> list = new ArrayList<>();
for (int i = 1; i <= 3; i++) {
Thread thread = new Thread(() -> {
log.info("{} 执行~", Thread.currentThread().getName());
},"t" + i);
list.add(thread);
}
for (int i = 0; i < list.size(); i++) {
list.get(i).start();
//他阻塞的是主线程,当前线程执行完后,主线程才会执行
list.get(i).join();
}
}
2、通过CountDownLatch 的方式
countDown():没调用一次,计数器就会减 1。当计数器减到 0 时,调用await()的线程才会被唤醒。
await():当计数器不为 0 的时候,调用await()的线程会被阻塞。
java
/**
* 1、让第一个线程执行,完事调用countDown()再对countDownLatch记数减 1
* 2、第二个线程调用await()去唤醒,但是需要线程一执行完,
* 因为只有记数为 0 的话,当前线程才会被唤醒,依次类推
*/
@Test
void testMethod2(){
CountDownLatch countDownLatch = new CountDownLatch(1);
for (int i = 1; i <= 3; i++) {
final int threadId = i;
new Thread(() -> {
if (threadId != 1) {
try {
//等待计数器归零,再继续往下执行,否则在此处阻塞
countDownLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
log.info("{} 执行~", Thread.currentThread().getName());
//计数器减 1
countDownLatch.countDown();
},"t" + i).start();
}
}
3、通过Semaphore的方式
通过信号量控制,每个线程在开始执行前需要获取一个许可,执行完毕后释放下一个线程需要的许可。
java
/**
* 只让第一个信号量有一个许可,另外两个信号量没有许可
* 1、第一个线程获取到许可,执行打印,然后释放下一个信号量的许可
* 2、第二个线程获取到许可,执行打印,然后释放下一个信号量的许可
* 3、第三个线程获取到许可,执行打印
*/
@Test
void testMethod3(){
int length = 3;
List<Semaphore> semaphoreList = new ArrayList<>();
for (int i = 1; i <= length; i++) {
Semaphore semaphore = new Semaphore(i == 1 ? 1 : 0);
semaphoreList.add(semaphore);
}
for (int j = 0; j < length; j++) {
final int theadIndex = j;
new Thread(() -> {
try {
//获取许可
semaphoreList.get(theadIndex).acquire();
log.info("{} 执行~", Thread.currentThread().getName());
} catch (InterruptedException e) {
throw new RuntimeException(e);
}finally {
//释放许可
if (theadIndex != length - 1) {
semaphoreList.get(theadIndex + 1).release();
}
}
},"t" + (j + 1)).start();
}
}
4、CompletableFuture的方式
原理:异步任务链式调用
优势:代码简洁,内置线程池管理
java
CompletableFuture.runAsync(() -> System.out.println("t1"))
.thenRun(() -> System.out.println("t2"))
.thenRun(() -> System.out.println("t3"))
.join(); // 阻塞等待全部完成
5、利用线程池
原理:所有任务提交到单线程队列顺序执行
优势:自动管理线程生命周期
注意:实际是同一个线程执行所有任务
java
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(() -> System.out.println("t1"));
executor.submit(() -> System.out.println("t2"));
executor.submit(() -> System.out.println("t3"));
executor.shutdown();