首先在操作系统层面,宏观角度上来讲,线程的生命周期是五态
1. 初始状态
,
指的是线程已经被创建,但是还不允许分配 CPU 执行。这个状态属于编程
语言特有的,不过这里所谓的被创建,仅仅是在编程语言层面被创建,而在操作系统层
面,真正的线程还没有创建。
2. 可运行状态
,指的是线程可以分配 CPU 执行。在这种状态下,真正的操作系统线程已
经被成功创建了,所以可以分配 CPU 执行。
3. 当有空闲的 CPU 时,操作系统会将其分配给一个处于可运行状态的线程,被分配到
CPU 的线程的状态就转换成了
运行状态
。
4. 运行状态的线程如果调用一个阻塞的 API(例如以阻塞方式读文件)或者等待某个事件
(例如条件变量),那么线程的状态就会转换到
休眠状态
,同时释放 CPU 使用权,休眠
状态的线程永远没有机会获得 CPU 使用权。当等待的事件出现了,线程就会从休眠状态
转换到可运行状态。
5. 线程执行完或者出现异常就会进入
终止状态
,终止状态的线程不会切换到其他任何状
态,进入终止状态也就意味着线程的生命周期结束了。
然而在JAVA层面 是六种
1. NEW(初始化状态)
2. RUNNABLE(可运行状态+运行状态)
3. BLOCKED(阻塞状态)
4. WAITING(无时限等待)
5. TIMED_WAITING(有时限等待)
6. TERMINATED(终止状态)
![](https://i-blog.csdnimg.cn/blog_migrate/4883d2777cb450b772c173c936919919.png)
方式1:使用 Thread类或继承Thread类
1
//
创建线程对象
2
Thread t
=
new
Thread
() {
3
public void
run
() {
4
//
要执行的任务
5
}
6
};
7
//
启动线程
8
t
.
start
();
方式2:实现 Runnable 接口配合Thread
把【线程】和【任务】(要执行的代码)分开
Thread 代表线程
Runnable 可运行的任务(线程要执行的代码)
1
Runnable runnable
=
new
Runnable
() {
2
public void
run
(){
3
//
要执行的任务
4
}
5
};
6
//
创建线程对象
7
Thread t
=
new
Thread
(
runnable
);
8
//
启动线程
9
t
.
start
();
方式3:使用有返回值的 Callable
1
class
CallableTask
implements
Callable
<
Integer
>
{
2
@Override
3
public
Integer call
()
throws Exception
{
4
return new
Random
().
nextInt
();
5
}
6
}
7
//
创建线程池
8
ExecutorService service
=
Executors
.
newFixedThreadPool
(
10
);
9
//
提交任务,并用
Future
提交返回结果
10
Future
<
Integer
>
future
=
service
.
submit
(
new
CallableTask
());
方式4:使用 lambda
1
new
Thread
(()
‐
>
System
.
out
.
println
(
Thread
.
currentThread
().
getName
())).
start
();
Runnable和Callable的区别
Callable有返回值 可以捕获异常,依据于线程池
创建线程的方式 分为4这种 但其实底层都是一种创建方式
实际上还是调用了Runnable的run方法
也就是说第一种第二种和第四种是一样的方式创建线程
线程池的底层也是使用Thread来创建线程
所以java提供的几种方式 可以当做一个工具类 开发人员可以选择不同的方式来调用
Version:0.9 StartHTML:0000000105 EndHTML:0000018120 StartFragment:0000000141 EndFragment:0000018080
Thread常用方法
sleep方法
调用 sleep 会让当前线程从
Running
进入TIMED_WAITING状态,
不会释放对象锁
其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出
InterruptedException,并且会清除中断标志
睡眠结束后的线程未必会立刻得到执行
sleep当传入参数为0时,和yield相同
yield方法
yield会释放CPU资源,让当前线程从 Running 进入 Runnable状态,让优先级更高
(至少是相同)的线程获得执行机会,
不会释放对象锁
;
假设当前进程只有main线程,当调用yield之后,main线程会继续运行,因为没有比
它优先级更高的线程;
具体的实现依赖于操作系统的任务调度器
join方法
等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之
后才能继续运行的场景。
1
public class
ThreadJoinDemo
{
2
3
public static void
main
(
String
[]
sure
)
throws InterruptedException
{
4
5
Thread t
=
new
Thread
(
new
Runnable
() {
6
@Override
7
public void
run
() {
8
System
.
out
.
println
(
"t begin"
);
9
try
{
10
Thread
.
sleep
(
5000
);
11
}
catch
(
InterruptedException e
) {
12
e
.
printStackTrace
();
13
}
14
System
.
out
.
println
(
"t finished"
);
15
}
16
});
17
long start
=
System
.
currentTimeMillis
();
18
t
.
start
();
19
//
主线程等待线程
t
执行完成
20
t
.
join
();
21
22
System
.
out
.
println
(
"
执行时间
:"
+
(
System
.
currentTimeMillis
()
‐
start
));
23
System
.
out
.
println
(
"Main finished"
);
24
}
25
}
如何正确优雅的停止线程?
stop方法
stop()方法已经被jdk废弃,原因就是stop()方法太过于暴力,强行把执行到一半的线程终止。
1
public class
ThreadStopDemo
{
2
3
private static
Object lock
=
new
Object
();
4
5
public static void
main
(
String
[]
args
)
throws InterruptedException
{
6
7
Thread thread
=
new
Thread
(
new
Runnable
() {
8
@Override
9
public void
run
() {
10
synchronized
(
lock
) {
11
System
.
out
.
println
(
Thread
.
currentThread
().
getName
()
+
"
获取锁
"
);
12
try
{
13
Thread
.
sleep
(
60000
);
14
}
catch
(
InterruptedException e
) {
15
e
.
printStackTrace
();
16
}
17
}
18
System
.
out
.
println
(
Thread
.
currentThread
().
getName
()
+
"
执行完成
"
);
19
}
20
});
21
thread
.
start
();
22
Thread
.
sleep
(
2000
);
23
//
停止
thread
,并释放锁
24
thread
.
stop
();
25
26
new
Thread
(
new
Runnable
() {
27
@Override
28
public void
run
() {
29
System
.
out
.
println
(
Thread
.
currentThread
().
getName
()
+
"
等待获取锁
"
);
30
synchronized
(
lock
) {
31
System
.
out
.
println
(
Thread
.
currentThread
().
getName
()
+
"
获取锁
"
);
32
}
33
}
34
}).
start
();
35
36
}
37
}
stop会释放对象锁,可能会造成数据不一致。
Java线程的中断机制
Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。
中断机制是一
种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。
被
中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选
择压根不停止。
API的使用
interrupt(): 将线程的中断标志位设置为true,不会停止线程
isInterrupted(): 判断当前线程的中断标志位是否为true,不会清除中断标志位
Thread.interrupted():判断当前线程的中断标志位是否为true,并清除中断标志
位,重置为fasle
1
public class
ThreadInterruptTest
{
2
3
static
int i
=
0
;
4
5
public static void
main
(
String
[]
args
) {
6
System
.
out
.
println
(
"begin"
);
7
Thread t1
=
new
Thread
(
new
Runnable
() {
8
@Override
9
public void
run
() {
10
while
(
true
) {
11
i
++
;
12
System
.
out
.
println
(
i
);
13
//Thread.interrupted()
清除中断标志位
14
//Thread.currentThread().isInterrupted()
不会清除中断标志位
15
if
(
Thread
.
currentThread
().
isInterrupted
() ) {
16
System
.
out
.
println
(
"========="
);
17
}
18
if
(
i
==
10
){
19
break
;
20
}
21
22
}
23
}
24
});
25
26
t1
.
start
();
27
//
不会停止线程
t1,
只会设置一个中断标志位
flag=true
28
t1
.
interrupt
();
29
}
30
}
利用中断机制优雅的停止线程
1
while
(
!
Thread
.
currentThread
().
isInterrupted
()
&&
more work to
do
) {
2
do
more work
3
}
1
public class
StopThread
implements
Runnable
{
2
3
@Override
4
public void
run
() {
5
int count
=
0
;
6
while
(
!
Thread
.
currentThread
().
isInterrupted
()
&&
count
<
1000
) {
7
System
.
out
.
println
(
"count = "
+
count
++
);
8
}
9
System
.
out
.
println
(
"
线程停止:
stop thread"
);
10
}
11
12
public static void
main
(
String
[]
args
)
throws InterruptedException
{
13
Thread thread
=
new
Thread
(
new
StopThread
());
14
thread
.
start
();
15
Thread
.
sleep
(
5
);
16
thread
.
interrupt
();
17
}
18
}
注意:使用中断机制时一定要注意是否存在中断标志位被清除的情况
sleep 期间能否感受到中断
修改上面的代码,线程执行任务期间有休眠需求
1
@Override
2
public void
run
() {
3
int count
=
0
;
4
while
(
!
Thread
.
currentThread
().
isInterrupted
()
&&
count
<
1000
) {
5
System
.
out
.
println
(
"count = "
+
count
++
);
6
7
try
{
8
Thread
.
sleep
(
1
);
9
}
catch
(
InterruptedException e
) {
10
e
.
printStackTrace
();
11
}
12
}
13
System
.
out
.
println
(
"
线程停止:
stop thread"
);
14
}
处于休眠中的线程被中断,
线程是可以感受到中断信号的,并且会抛出一个
InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false
。这样就会导致
while条件Thread.currentThread().isInterrupted()为false,程序会在不满足count < 1000这个
条件时退出。如果不在catch中重新手动添加中断信号,不做任何处理,就会屏蔽中断请求,有
可能导致线程无法正确停止。
1
try
{
2
Thread
.
sleep
(
1
);
3
}
catch
(
InterruptedException e
) {
4
e
.
printStackTrace
();
5
//
重新设置线程中断状态为
true
6
Thread
.
currentThread
().
interrupt
();
7
}
sleep可以被中断 抛出中断异常:sleep interrupted, 清除中断标志位
wait可以被中断 抛出中断异常:InterruptedException, 清除中断标志位
Java线程间通信
volatile
volatile有两大特性,一是可见性,二是有序性,禁止指令重排序,其中可见性就是可以让线程
之间进行通信。
1
public class
VolatileDemo
{
2
3
private static
volatile boolean flag
=
true
;
4
5
public static void
main
(
String
[]
args
) {
6
7
new
Thread
(
new
Runnable
() {
8
@Override
9
public void
run
() {
10
while
(
true
){
11
if
(
flag
){
12
System
.
out
.
println
(
"trun on"
);
13
flag
=
false
;
14
}
15
}
16
}
17
}).
start
();
18
19
new
Thread
(
new
Runnable
() {
20
@Override
21
public void
run
() {
22
while
(
true
){
23
if
(
!
flag
){
24
System
.
out
.
println
(
"trun off"
);
25
flag
=
true
;
26
}
27
}
28
}
29
}).
start
();
30
}
31
}
等待唤醒(等待通知)机制
等待唤醒机制可以基于wait和notify方法来实现,在一个线程内调用该线程锁对象的wait方法,
线程将进入等待队列进行等待直到被唤醒。
1
public class
WaitDemo
{
2
private static
Object lock
=
new
Object
();
3
private static
boolean flag
=
true
;
4
5
public static void
main
(
String
[]
args
) {
6
new
Thread
(
new
Runnable
() {
7
@Override
8
public void
run
() {
9
synchronized
(
lock
){
10
while
(
flag
){
11
try
{
12
System
.
out
.
println
(
"wait start ......."
);
13
lock
.
wait
();
14
}
catch
(
InterruptedException e
) {
15
e
.
printStackTrace
();
16
}
17
}
18
19
System
.
out
.
println
(
"wait end ....... "
);
20
}
21
}
22
}).
start
();
23
24
new
Thread
(
new
Runnable
() {
25
@Override
26
public void
run
() {
27
if
(
flag
){
28
synchronized
(
lock
){
29
if
(
flag
){
30
lock
.
notify
();
31
System
.
out
.
println
(
"notify ......."
);
32
flag
=
false
;
33
}
34
35
}
36
}
37
}
38
}).
start
();
39
}
40
}
LockSupport是JDK中用来实现线程阻塞和唤醒的工具,线程调用park则等待“许可”,调用
unpark则为指定线程提供“许可”。
使用它可以在任何场合使线程阻塞,可以指定任何线程进行
唤醒,并且不用担心阻塞和唤醒操作的顺序,但要注意连续多次唤醒的效果和一次唤醒是一样
的。
1
public class
LockSupportTest
{
2
3
public static void
main
(
String
[]
args
) {
4
Thread parkThread
=
new
Thread
(
new
ParkThread
());
5
parkThread
.
start
();
6
7
System
.
out
.
println
(
"
唤醒
parkThread"
);
8
LockSupport
.
unpark
(
parkThread
);
9
}
10
11
static class
ParkThread
implements
Runnable
{
12
13
@Override
14
public void
run
() {
15
System
.
out
.
println
(
"ParkThread
开始执行
"
);
16
LockSupport
.
park
();
17
System
.
out
.
println
(
"ParkThread
执行完成
"
);
18
}
19
}
20
}
管道输入输出流
管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程
之间的数据传输,而传输的媒介为内存。管道输入/输出流主要包括了如下4种具体实现:
PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,
而后两种面向字符。
1
public class
Piped
{
2
public static void
main
(
String
[]
args
)
throws Exception
{
3
PipedWriter out
=
new
PipedWriter
();
4
PipedReader
in
=
new
PipedReader
();
5
//
将输出流和输入流进行连接,否则在使用时会抛出
IOException
6
out
.
connect
(
in
);
7
8
Thread printThread
=
new
Thread
(
new
Print
(
in
),
"PrintThread"
);
9
10
printThread
.
start
();
11
int receive
=
0
;
12
try
{
13
while
((
receive
=
System
.
in
.
read
())
!= ‐
1
) {
14
out
.
write
(
receive
);
15
}
16
}
finally
{
17
out
.
close
();
18
}
19
}
20
21
static class
Print
implements
Runnable
{
22
private
PipedReader
in
;
23
24
public
Print
(
PipedReader
in
) {
25
this
.
in
=
in
;
26
}
27
28
@Override
29
public void
run
() {
30
int receive
=
0
;
31
try
{
32
while
((
receive
=
in
.
read
())
!= ‐
1
) {
33
System
.
out
.
print
((
char
)
receive
);
34
}
35
}
catch
(
IOException ex
) {
36
}
37
}
38
}
39
}
Thread.join
join可以理解成是线程合并,当在一个线程调用另一个线程的join方法时,当前线程阻塞等
待被调用join方法的线程执行完毕才能继续执行,所以join的好处能够保证线程的执行顺序,但
是如果调用线程的join方法其实已经失去了并行的意义,虽然存在多个线程,但是本质上还是串
行的,最后join的实现其实是基于等待通知机制的。