在一般小型开发项目中,其实用不到多线程,但是这个概念以及内容是非常重要的,牵扯到的知识点也非常多。
首先多线程的四种方式
1、集成Thread类
2、实现Runable接口
3、实现Callable接口
4、线程池
但其实开启多线程我们一般推荐用实现接口的方法而不是继承,因为在java中只能单继承多实现,如果通过继承开启多线程,少了很多扩展性,实现接口比较灵活,要给自己留后路。
而对于Runable和Callable来说,区别其实就是Callable可以自定义抛出异常。
线程中核心的两个方法是run(),start()方法,如果直接调用对象的run()方法只会运行一次,需要使用start()才能真正开启多线程。
顺带提一嘴线程的生命周期:新建,就绪,阻塞,运行,销毁
线程阻塞的方式:sleep(),wait(),join(),yield()
其中sleep和wait区别就是wait会释放对象锁,结束阻塞后会重新获取对象锁,跟wait()相关联的两个方法notify和notifyall,notify可能会导致死锁,并且sleep和join以及yield都是Thread方法,wait(),notify(),notifyall()都是Object方法
所有的理论都是为了最后的实践,接下来会讲解线程当中必不可少的CountDownLatch,CyclicBarrier,Semaphore使用方法和实例。
为什么会有这些方法的使用,最重要的就是有他所需要的场景
1、CountDownLatch,类似于用一个栅栏去挡住子线程,子线程每次会使计数减一,当计数完毕之1后,主线程调用await()方法继续后续的逻辑处理,类似场景如在很多计算量大的场景,使用多线程并发提高效率之后,需要保证主线程后续计算是子线程计算之后正确的值,就可以使用CountDownLatch进行主线程等待子线程执行完之后再进行执行。
实例
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
// 创建一个 CountDownLatch,计数为 5,表示需要等待 5 个任务完成
CountDownLatch latch = new CountDownLatch(5);
// 创建一个线程池
ExecutorService executor = Executors.newFixedThreadPool(5);
// 提交 5 个任务到线程池
for (int i = 0; i < 5; i++) {
final int taskId = i;
executor.submit(() -> {
System.out.println("线程 " + Thread.currentThread().getId() + " 正在执行任务 " + taskId);
// 模拟任务耗时
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
// 任务完成后,计数器减一
latch.countDown();
try {
latch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("所有任务已经完成,子线程继续执行");
});
}
// 主线程等待 CountDownLatch 计数归零
latch.await();
System.out.println("所有任务已经完成,主线程继续执行");
// 关闭线程池
executor.shutdown();
}
}
2、CyclicBarrier,原理和CountDownLatch类似,只不过在此处是由子线程调用await()方法进行计数减一和阻塞,当所有子线程都执行到阻塞节点之后,再统一允许后续代码。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
// 创建一个CyclicBarrier,指定参与同步的线程数量
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
// 创建并启动三个线程
new Thread(() -> {
try {
System.out.println("线程1准备完毕,等待其他线程...");
cyclicBarrier.await(); // 线程在此等待
System.out.println("线程1开始执行后续任务...");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println("线程2准备完毕,等待其他线程...");
cyclicBarrier.await(); // 线程在此等待
System.out.println("线程2开始执行后续任务...");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println("线程3准备完毕,等待其他线程...");
cyclicBarrier.await(); // 线程在此等待
System.out.println("线程3开始执行后续任务...");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
3、Semaphore,类似于给予许可权限,线程才能执行后续代码,比如说设置五个许可,给线程池提交了十个任务,那么同时并发执行后续的任务最多只有五个,会通过semaphore.acquire()获取许可,有剩余许可继续执行,没有则进行等待,这里要注意,无论是否正确执行,都要释放许可semaphore.release()
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
// 假设有一个资源池,比如数据库连接池,最大只能支持5个并发访问
private final Semaphore semaphore = new Semaphore(5);
public void doSomethingWithResource() {
try {
// 获取许可,如果没有许可则等待
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + " 获得了许可,正在访问资源...");
// 模拟资源访问的耗时操作
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " 释放了许可,完成资源访问...");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
// 无论是否发生异常,都要释放许可
semaphore.release();
}
}
public static void main(String[] args) {
SemaphoreExample example = new SemaphoreExample();
ExecutorService executor = Executors.newFixedThreadPool(10); // 创建一个固定大小的线程池
// 提交10个任务到线程池
for (int i = 0; i < 10; i++) {
final int taskId = i;
executor.submit(() -> {
example.doSomethingWithResource();
});
}
// 关闭线程池(这里只是示例,实际使用时可能需要更复杂的逻辑来优雅关闭线程池)
executor.shutdown();
}
}