深入理解Java高并发编程(2) - 线程基础
1. 线程创建 & callable
线程创建的方法:
-
直接通过Thread类创建线程。
- 通过实例Thread类(匿名内部类),重写thread的run方法(因为本身的run方法是没有功能上的实现的,相当于是构造了匿名内部类),创建线程。再通过start方法启动线程。
-
通过runnable接口创建线程。
- runnable接口是一个函数式接口,只有一个run方法,通过
Thread t = new Thread(runnable)将runnable对象(lambda表达式或者匿名内部类)传给thread构造方法,本质上是thread对象会检查自己有没有runnable类型的属性(构造方法接受runnable对象,会把这个对象传给thread的target属性),而对于thread的run方法中,如果target不为空,就会调用target上的run方法,也就是传过来的lambda表达式的方法。

- 这种方法通过把线程和任务分开,更加灵活。
- runnable接口是一个函数式接口,只有一个run方法,通过
-
FutureTask通过实现Runnabletask间接实现了runnable接口处理不带返回值的任务(runnable),除此之外,FutureTask还继承了Future接口间接实现了Callable接口,用于接受任务返回结果。FutureTask通过接收一个Callable类型(runnable的增强)的lambda表达式, 处理带返回值的任务
FutureTask<Integer> task = new FutureTask(() -> return 1);
Thread t = new thread(task);
Runnable vs Callable
- callable 是带返回值的任务,而runnable不带返回值
- runnable只能抛出非受检异常(runtimeException),而callable会抛出任意异常(后面线程池中有例子)
- runnable可以直接传给thread执行,callable必须先包装成FutureTask再交给Thread或者直接通过线程池执行。
- 当调用callable包装的futureTask的get方法时,需要等待方法的结果,会阻塞线程
2.线程运行原理
2.1 私有栈内存
jvm运行区内存中的栈是每个线程私有的,当每调用一个方法,都会有一个栈帧,栈顶的栈帧叫做活动栈帧,栈帧中会记录 局部变量表,
操作数栈,动态连接,返回地址,其中返回地址是记录上一个栈帧的地址,当活动栈帧的方法被执行完了就会返回到上一个栈帧的地址。
当所有的线程(非守护线程)执行结束时,程序也就执行结束了。
2.2 线程上下文切换
线程上下文切换指的是当前线程状态被保存,恢复被分配时间片的内存的状态的过程,线程上下文切换存在一定消耗。
以下情况会导致线程上下文切换
- cpu时间片用完
- gc
- 更高优先级线程需要运行
- 线程调用了sleep,yield,wait,join,park,synchronize,lock等方法
3. 线程方法
3.1 为什么不能用run方法代替 start方法
run方法声明了线程具体要执行的方法,start是当前线程启动另一个线程,如果直接在当前线程中调用run方法,效果就是当前线程执行了run方法,而不是start的新创建线程执行run方法,不能实现异步的效果。
3.2 interrupt 方法
interrupt方法可以打断阻塞状态/运行状态的线程,如调用了 sleep,wait,join的方法
- 打断阻塞状态的线程会抛出一个
IntegerruptedException,同时会有一个打断标记,如果打断 sleep,join,wait的线程,会把打断标记设置为false,通过异常的方式表示被打断了 - 打断正常运行的线程不会立即让运行的线程停止,而是线程中通过判断打断标记来决定是不是要停下来
获取打断标记:
isInterrupted():非静态方法,判断是否被打断,不会清除打断标记Interrupted():静态方法,同样是判断是否被打断,但是会将打断标记重新设置为false
interrupt打断park线程:
当打断标记为true时,再执行park方法就不能让线程停下来了。
4. 守护线程
默认来说,Java进程需要等待所有线程都运行完毕再结束,守护线程是一种特殊线程,这种线程是特点是即使守护线程线程没运行完,非守护线程运行完了进程也会结束。
通过setDaemon()方法设置守护线程。
垃圾回收线程就是一种常见的守护线程。
5. 线程状态
5.1 操作系统层面的五种状态
- 初始状态
- 仅在语言层面创建了线程对象,还未与操作系统系统关联
- 可运行状态
- 线程被创建,且可以由cpu分配时间片调度执行
- 运行状态
- 获取cpu时间片运行中的线程,当时间片消耗尽会变为可运行状态,这时候会发生上下文的切换。
- 阻塞状态
- 调用阻塞API,如读写文件,这些线程实际是不会用到cpu的,也会发生上下文切换。
- 终止状态
- 线程已经执行完毕,生命周期结束,不会再进入其他状态。

- 线程已经执行完毕,生命周期结束,不会再进入其他状态。
5.2 Java语言层面上的6种状态
- New
- 线程对象刚刚被创建,还没有执行start方法
- Runnable
- 包括操作系统层面的运行,可运行,阻塞状态,也就是获取时间片,没获取时间片,读写IO造成的阻塞的情况(阻塞)
- wait(Java中的阻塞,调度器不会分配时间片)
- 没传时间参数的join,park出现的状态
- TimedWaiting(Java中的阻塞,调度器不会分配时间片)
- 一般是调用了传时间参数的sleep join 等方法时候出现的状态,park方法是没有时间参数的,parkNanos(long nanos)/parkUntil(long millis)能实现带超时的park,让线程进入TimedWaiting状态
- block(Java中的阻塞,调度器不会分配时间片)
- 竞争锁的时候就会出现block状态
- terminated

浙公网安备 33010602011771号