【Java并发 01】Java线程并发初窥
文章目录
龙云尧个人博客,转载请注明出处。 CSDN地址:http://blog.csdn.net/michael753951 个人blog地址:http://yaoyl.cn
知识储备
我们知道Java中线程的创建是使用Thread进行创建。在编写简单的测试代码或者业务代码的时候,我们可能也直接使用new Thread的方式进行线程任务的创建。
但是这种方式真的能够满足业务诉求么?或者说这些写出来的代码是否还有继续调优的空间?
这些问题在资源紧张或者高响应的架构上,可能就需要进行考量了。
本文主要基于Java中的线程及其并发库进行初窥。
操作系统API
首先我们看,我们程序运行的操作系统,给我们的线程的运行,提供了哪些API的支持,基于此我们再来看Java 的实现。这里我以标准的**POSIX(Portable Operating System Interface,可移植操作系统接口)**中的标准库Pthreads为例。
Pthreads中定义了一组线程操作函数接口(本次仅分析操作部分):
1pthread_create() // 创建一个线程
2pthread_exit() //终止当前线程
3pthread_cancel() // 请求中断另外一个线程的运行。被请求中断的线程会继续运行,直至到达某个取消点(Cancellation Point)。取消点是线程检查是否被取消并按照请求进行动作的一个位置。POSIX 的取消类型(Cancellation Type)有两种,一种是延迟取消(PTHREAD_CANCEL_DEFERRED),这是系统默认的取消类型,即在线程到达取消点之前,不会出现真正的取消;另外一种是异步取消(PHREAD_CANCEL_ASYNCHRONOUS),使用异步取消时,线程可以在任意时间取消。系统调用的取消点实际上是函数中取消类型被修改为异步取消至修改回延迟取消的时间段。几乎可以使线程挂起的库函数都会响应CANCEL信号,终止线程,包括sleep、delay等延时函数。
4pthread_join() // 阻塞当前的线程,直到另外一个线程运行结束
5pthread_kill() // 向指定ID的线程发送一个信号,如果线程不处理该信号,则按照信号默认的行为作用于整个进程。信号值0为保留信号,作用是根据函数的返回值判断线程是不是还活着。
6pthread_cleanup_push() // 线程可以安排异常退出时需要调用的函数,这样的函数称为线程清理程序,线程可以建立多个清理程序。线程清理程序的入口地址使用栈保存,实行先进后处理原则。由pthread_cancel或pthread_exit引起的线程结束,会次序执行由pthread_cleanup_push压入的函数。线程函数执行return语句返回不会引起线程清理程序被执行。
7pthread_cleanup_pop() // 以非0参数调用时,引起当前被弹出的线程清理程序执行。
8pthread_setcancelstate() // 允许或禁止取消另外一个线程的运行。
9pthread_setcanceltype() // 设置线程的取消类型为延迟取消或异步取消。
所有操作函数均引用自POSIX线程 - 函数部分,全量接口可以参考
pthread.h
中所有接口。
java.lang.Thread提供的API
上述接口看上去是不是特别眼熟,我们在Java的标准线程库java.lang.Thread
中,翻出了很多类似的对外提供的接口:
1Thread currentThread(); // 获取当前线程的引用
2void yield(); // 线程让步(暂时放弃CPU资源,让线程重新回到RUNNABLE状态,不接受中断)
3void sleep(long millis) throws InterruptedException; // 线程休眠
4void start(); // 线程实际创建函数
5void join(final long millis) throws InterruptedException; // 阻塞主线程,等待子线程任务执行完毕
这里其实已经不难发现,Java中Thread提供的接口的定义和功能,很大程度上其实是和Posix线程接口是很相似的。
翻看Java的Thread源码,我们可以发现Java中的线程状态有6个,状态转换图如下:
对比操作系统的线程状态模型,我们发现Java中的线程状态更加复杂(核心原因是因为Java开发人员需要更加细粒度的管理线程抢占情况)
同时我们可以从操作系统的线程调度接口
线程池
从曾经的项目经验,以及我们在各个前辈的摸爬滚打积攒的知识的浸淫之下,我们知道在多线程编程下,通常使用线程池会比我们单纯的使用线程进行业务管理效率更高,管理也更加方便。这里膜拜一下Doug Lea教授,能够自己设计并且手撸出来线程池中的核心代码并一直在维护。
在Java中,Thread的之心过程中主要的任务执行体是Runnable,Doug Lea
老爷子专门定义了一个Runnable提交接口ExecutorService
,其中ExecutorService
对外提供的主要接口有:
1shutdown(); // 尝试关闭ExecutorService,但不会立刻执行
2shutdownNow(); // 通过发起终端的形式尝试立刻停止线程,但是其实也不能保证所有的线程一定能够停止
3Future<?> submit(Runnable task); // 提交Runnable任务,等待ExecutorService执行
4<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException; // 尝试执行所有提交的线程
5<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException; // 只要有线程成功,则停止其他所有线程
上述接口的具体实现原理,以及如何合理使用,我会在以后逐步分析到。
ExecutorService
拥有其默认实现抽象类AbstractExecutorService
,并且AbstractExecutorService
作为基础抽象类,扩展出来了当前主流的几种线程池方案:
- ThreadPoolExecutor: 最经典的线程池执行实体类
- ScheduledThreadPoolExecutor: 实际上是ThreadPoolExecutor的扩展类,定时执行线程作为能力补充;
- ForkJoinPool: Fork-Join框架是
Doug Lea
老爷子在Java1.7新引入功能,而上述两个线程池技术是Java 1.5就已经引入的API。
其他补充知识
- 操作系统知识,这个需要勤看书
- 源码知识,这个就需要继续读代码了
测试代码
1import java.util.ArrayList;
2import java.util.List;
3import java.util.concurrent.Executor;
4import java.util.concurrent.ExecutorService;
5import java.util.concurrent.Executors;
6
7public class TimeTest {
8 public static void main(String[] args) throws InterruptedException {
9 // 创建一个线程独有的变量
10 ThreadLocal<Object> obj = new ThreadLocal<>();
11 Runnable runnable = () -> {
12 if (obj.get() == null) obj.set(new Object());
13 };
14 System.out.println("multiple thread cost: " + new TestThread().testMultiTaskByThread(runnable));
15 System.out.println("thread pool cost: " + new TestThreadPool().testMultiTaskByThreadPool(runnable));
16 System.out.println("ForkJoinTask cost: " + new TestForkJoinTask().testMultiTaskByForkJoinPool(runnable));
17 System.out.println("Stream.parallel cost: " + new TestStreamParallel().testStreamParallel(runnable));
18 }
19}
20
21class TestThread {
22 long testMultiTaskByThread(Runnable runnable) throws InterruptedException {
23 long current = System.currentTimeMillis();
24 for (int i = 0; i < 100_000; i++) {
25 Thread thread = new Thread(runnable);
26 thread.start();
27 thread.join();
28 }
29 return System.currentTimeMillis() - current;
30 }
31}
32
33class TestThreadPool {
34 long testMultiTaskByThreadPool(Runnable runnable) {
35 ExecutorService executor = Executors.newFixedThreadPool(8);
36 long current = System.currentTimeMillis();
37 for (int i = 0; i < 100_000; i++) {
38 executor.execute(runnable);
39 }
40 executor.shutdownNow();
41 return System.currentTimeMillis() - current;
42 }
43}
44
45class TestForkJoinTask {
46 long testMultiTaskByForkJoinPool(Runnable runnable) {
47 Executor executor = Executors.newWorkStealingPool();
48 long current = System.currentTimeMillis();
49 for (int i = 0; i < 100_000; i++) {
50 executor.execute(runnable);
51 }
52 return System.currentTimeMillis() - current;
53 }
54}
55
56class TestStreamParallel {
57 long testStreamParallel(Runnable runnable) {
58 long current = System.currentTimeMillis();
59 List<Integer> countList = new ArrayList<>();
60 for (int i = 0; i < 100_000; i++) {
61 countList.add(i);
62 }
63 countList.parallelStream().forEach(i -> runnable.run());
64 return System.currentTimeMillis() - current;
65 }
66}
测试结果
1multiple thread cost: 9691
2thread pool cost: 26
3ForkJoinTask cost: 32
4Stream.parallel cost: 8