并发分工问题的解决模式
并发分工模式
前言引入
并发三大问题就是互斥、同步、分工,这三大问题JAVA都提供了解决方案,如互斥可以使用互斥锁解决,同步可以采用管程原语解决,分工都是采用Fork/join、线程池解决等等,不过这些都是微观方面,如何从宏观层面去了解解决这些问题呢?这里以分工为例。
分工的解决方案在并发编程中有很多模式如Thread-Per-Message模式,Worker Thread 模式等等这里将这两个模式拆开进行对比理解。
Thread-Per-Message模式
Thread-Per-Message模式可以从现实世界找到答案,我们租房子自己没空是不是将租住房子这件事委托给了中介,中介替我们去租房子,我们不需要关心细节。
在编程领域例如我们在主线程中实现了一个HTTP Server程序,那么在同一时刻主程序就无法响应多个程序的请求,这时候就可以开启多线程模式,主线程接收请求,子线程处理请求。
简而言之就是为每一个任务分配一个线程的过程就可以称为Thread-Per-Message模式。
Thread实现Thread-Per-Message
Thread-Per-Message模式应用场景比较典型的是在网络编程中,为每一个连接创建一个线程,连接关闭线程也就销毁结束,以上面代码为例。
public static void main(String[] args) throws IOException {
// 绑定端口
final ServerSocketChannel ssc=
ServerSocketChannel.open().bind(
new InetSocketAddress(8080));
//处理请求
try {
while (true) {
// 接收请求
SocketChannel sc = ssc.accept();
// 每个请求都创建一个线程
new Thread(()->{
try {
// 读Socket
ByteBuffer rb = ByteBuffer.allocateDirect(1024);
sc.read(rb);
// 忽略处理
// 关闭Socket
sc.close();
}catch(Exception e){
throw new UncheckedIOException((IOException) e);
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
ssc.close();
}
}
稍微了解Thread的人都应该知道,线程本来就是一个重量级别的对象,线程的创建和销毁带来了极大的资源消耗,这种方案在实际应用中肯定是不具备实用性的,既然这样用线程池是否可行呢?当然是可行的但是引入线程池不是最佳的,最佳方案应该是轻量级线程Fiber。
轻量级线程Fiber
轻量级线程在Java中其实使用很少,主要应用在Go语言、Lua语言中的协程,其本质就是轻量级别的线程,创建成本低,基本上和普通对象是差不多的,并且创建线程的速度和内存占用情况远超于Java本身依靠操作系统创建的线程。
在OpenJDK 有个 Loom 项目,就是要用Java 语言实现轻量级线程问题,这就是Fiber。
Worker Thread 模式
Worker Thread模式简单理解就是工厂工人,有活大家一起动手干,没活大家就都歇着,工人的数量基本上是固定的,这个和Thread-Per-Message是不同的,如下所示。
根据上图很容易就是得到Worker Thread模式的代码实现,采用阻塞队列存储推送的任务,然后每个工人消费阻塞队列中的任务,这不就是简易的线程池吗?确实如此,对于上面网络编程的代码只需要少许改动即可。
public static void main(String[] args) throws IOException {
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 绑定端口
final ServerSocketChannel ssc=
ServerSocketChannel.open().bind(
new InetSocketAddress(8080));
//处理请求
try {
while (true) {
// 接收请求
SocketChannel sc = ssc.accept();
// 每个请求都创建一个线程
executorService.execute(()->{
try {
// 读Socket
ByteBuffer rb = ByteBuffer.allocateDirect(1024);
sc.read(rb);
// 忽略处理
// 关闭Socket
sc.close();
}catch(Exception e){
throw new UncheckedIOException((IOException) e);
}
});
}
} catch (IOException e) {
e.printStackTrace();
} finally {
ssc.close();
executorService.shutdown();
}
}
线程池的注意事项
Worker Thread 模式就可以直接采用线程池实现,但是线程池一定要注意如下几个方面。
- 创建线程池不要使用Executors的静态方法,必须采用ThreadPoolExecutor手动创建,这也是在阿里巴巴编码规范中特意提到的。
- 创建线程池时工作队列不要使用无界队列,无界队列可能有OOM的风险。
- 创建线程池拒绝策略一定要明确(默认采用拒绝抛异常的形式)。
- 创建线程池时最好按照业务给线程命名,方便排查问题。
- 有任务依赖关系的线程不建议采用相同的线程池,这样可能造成线程死锁,提交相同线程池的线程一定是任务独立的。
总结
Thread-Per-Message模式和Worker Thread 模式两个都是解决分工问题,但是需要明确它们的区别。
- Thread-Per-Message模式是委托他人办理任务,WorkerThread模式是工厂工人的工作模式,模式定义不同。
- Thread-Per-Message模式是为每个任务分配一个线程理论是有多少个任务就分配多少个线程,而WorkerThread是有限的线程完成所有的任务。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~