1. 线程控制
1) 启动线程
通过调用 Thread 类的 start 方法来启动一个线程,这时此线程是处于就绪状态,并没有运行。
得到 CPU 时间片后,线程就开始自动执行 run 方法,run 方法被称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随即终止。
run 方法当作普通方法的方式调用。程序还是要顺序执行,要等待 run 方法体执行完毕后,才可继续执行下面的代码; 程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。
2) 暂停、恢复和停止线程
暂停(suspend)、恢复(resume)和停止(stop),这些 API 是已过期的,不建议使用。
不建议使用的原因:
(1) 以 suspend 方法为例,在调用后,线程不会释放已经占有的资源(比如锁),而是占有着资源进入睡眠状态,这样容易引发死锁问题;
(2) stop 方法在终结一个线程时不会保证线程的资源正常释放,通常是没有给予线程完成资源释放工作的机会,因此会导致程序可能工作在不确定状态下;
(3) 因为 suspend、resume 和 stop 方法带来的副作用,这些方法才被标注为不建议使用的过期方法;
3) 中断线程
(1) 使用退出标志
当 run 方法执行完后,线程就会退出。但有时 run 方法是永远不会结束的,如在服务端程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务。
在这种情况下,一般是将这些任务放在一个循环中,如 while 循环。如果想使 while 循环在某一特定条件下退出,最直接的方法就是设一个 boolean 类型的标志,并通过设置这个标志为 true 或 false来控制 while 循环是否退出。
(2) 使用 interrupt 方法
interrupt 方法中断线程:设置线程的中断状态位为 true,线程不时地检测中断标示位,判断线程是否应该被中断 (中断标示值是否为 true)。
interrupt 方法只是改变中断状态,不会中断一个正在运行的线程,需要用户自己去监视线程的状态为并做处理。
这一方法实际完成的是,给受阻塞的线程发出一个中断信号,这样受阻线程检查到中断标识,就得以退出阻塞的状态。
更确切的说,如果线程被Object.wait, Thread.join 和 Thread.sleep 三种方法之一阻塞,此时调用该线程的 interrupt() 方法,那么该线程将抛出一个 InterruptedException 中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用 interrupt()将不起作用,直到执行到 wait()、 sleep() 或 join() 时,才会抛出 InterruptedException。
a) 使用 interrupt() + InterruptedException 来中断线程:
线程处于阻塞状态,如Thread.sleep、wait、IO阻塞等情况时,调用interrupt方法后,sleep等方法将会抛出一个InterruptedException。
b) 使用 interrupt() + isInterrupted() 来中断线程:
this.interrupted(): 测试当前线程是否已经中断(静态方法)。如果连续调用该方法,则第二次调用将返回false。在api文档中说明 interrupted()方法具有清除状态的功能。执行后具有将状态标识清除为false的功能。
this.isInterrupted(): 测试线程是否已经中断,但是不能清除状态标识。
4) join 线程
join 方法是 Thread类 提供的让一个线程等待另一个线程完成的方法。格式:
thread.start();
thread.join();
5) 后台线程
后台线程(Daemon Thread)是在后台运行的,它的任务是为其他的线程提供服务,也被称为守护线程或精灵线程。后台线程的特征:如果所有的前台线程都死亡,后台线程会自动死亡。
调用 Thread 对象的 setDaemon(true) 方法可以将一个指定的线程设置为后台线程。格式:
thread.setDaemon(true);
thread.start();
6) 线程睡眠
线程调用 sleep() 方法进入阻塞状态后,在其睡眠时间段内,该线程不会获得执行的机会,即使系统中没有其他可执行的线程,处于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行。
Thread 类为睡眠线程提供了两种方法:
public static void sleep(long miliseconds)
public static void sleep(long miliseconds,int nanos)
参数声明:
miliseconds - 以毫秒为单位的睡眠时间。
nanos - 这是 0-999999 额外纳秒的睡眠时间。
7) 线程让步
线程让步 yield()方法作用是:暂停当前正在执行的线程对象,并执行其他线程。
但是,实际中无法保证 yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
8) sleep() 和 yield()的区别:
(1) sleep() 方法让线程进入阻塞状态,其他所有处于就绪状态的线程都有机会执行,yield() 方法会让线程重新回到就绪状态,但是只有优先级等于或者大于他的线程才会被执行;
(2) 使用 sleep() 方法需要捕获异常,yield() 不需要;
(3) sleep() 方法比 yield() 方法有更好的移植性,不建议使用yield();
实例:
1 public class App {
2 public static volatile boolean stopFlag = false;
3
4 public static void main( String[] args ) {
5
6 // 使用退出标志中断线程
7 testFlagExit();
8 System.out.println("----------------------------------------");
9
10 // 或使用 interrupt() + InterruptedException 中断线程
11 testInterruptExit();
12 System.out.println("----------------------------------------");
13
14 // 使用 interrupt() + isInterrupted() 中断线程
15 testIsInterruptedExit();
16 System.out.println("----------------------------------------");
17
18 // Thread join
19 ThreadJoinRunnable threadJoin1 = new ThreadJoinRunnable("Thread Join 1");
20 ThreadJoinRunnable threadJoin2 = new ThreadJoinRunnable("Thread Join 2");
21 ThreadJoinRunnable threadJoin3 = new ThreadJoinRunnable("Thread Join 3");
22 try {
23 threadJoin1.start();
24 threadJoin1.join();
25 threadJoin2.start();
26 threadJoin2.join();
27 threadJoin3.start();
28 threadJoin3.join();
29 } catch (InterruptedException e) {
30 e.printStackTrace();
31 }
32
33 }
34
35 // 使用退出标志中断线程
36 public static void testFlagExit() {
37
38 Thread t = new Thread(new Runnable() {
39 public void run() {
40 String name = Thread.currentThread().getName();
41 int i = 1;
42 try {
43
44 for (; i <= 10; i++) {
45 if (stopFlag) {
46 break;
47 }
48 System.out.println(name + ": (" + i + ") stopFlag = " + stopFlag);
49 Thread.sleep(1000);
50 }
51 } catch (InterruptedException e) {
52 System.out.println(name + ": interrupted");
53 }
54 System.out.println(name + ": (" + i + ") stopFlag = " + stopFlag + ", exit");
55 }
56 }, "Thread Flag" );
57 t.start();
58
59 try {
60 Thread.sleep(5000);
61
62 System.out.println("main: stopping " + t.getName() + ", set stopFlag = true");
63 stopFlag = true;
64
65 Thread.sleep(1000);
66
67 } catch (InterruptedException e) {
68 e.printStackTrace();
69 }
70
71 }
72
73 // 或使用 interrupt() + InterruptedException 中断线程
74 public static void testInterruptExit() {
75 Thread t = new Thread(new Runnable() {
76 public void run() {
77 String name = Thread.currentThread().getName();
78 try {
79
80 for (int i = 1; i <= 10; i++) {
81 System.out.println(name + ": (" + i + ")");
82 Thread.sleep(1000);
83 }
84 } catch (InterruptedException e) {
85 System.out.println(name + ": interrupted");
86 }
87 System.out.println(name +": exit");
88 }
89 }, "Thread Interrupt" );
90 t.start();
91
92 try {
93 Thread.sleep(5000);
94
95 System.out.println("main: stopping " + t.getName() + ", call interrupt()");
96 t.interrupt();
97
98 Thread.sleep(1000);
99 } catch (InterruptedException e) {
100 e.printStackTrace();
101 }
102 }
103
104 // 使用 interrupt() + isInterrupted() 中断线程
105 public static void testIsInterruptedExit() {
106 Thread t = new Thread(new Runnable() {
107 public void run() {
108 String name = Thread.currentThread().getName();
109 Boolean b = Thread.currentThread().isInterrupted();
110 int i = 1;
111
112 while(!b) {
113 System.out.println(name + ": isInterrupted(" + i + ") = " + b);
114 b = Thread.currentThread().isInterrupted();
115 i++;
116 }
117
118 System.out.println(name +": isInterrupted(" + i + ") = " + b + ", exit");
119 }
120 }, "Thread IsInterrupted" );
121 t.start();
122
123 try {
124 Thread.sleep(1);
125
126 System.out.println("main: stopping " + t.getName() + ", call interrupt()");
127 t.interrupt();
128
129 Thread.sleep(1000);
130 } catch (InterruptedException e) {
131 e.printStackTrace();
132 }
133 }
134 }
135
136 class ThreadJoinRunnable extends Thread implements Runnable {
137
138 ThreadJoinRunnable(String name) {
139 this.setName(name);
140 }
141
142 public void run() {
143 try {
144 System.out.println(getName());
145 Thread.sleep(1000);
146 } catch (InterruptedException e) {
147 System.out.println(getName() + ": interrupted");
148 }
149 System.out.println(getName() + ": exit");
150 }
151 }
输出:
Thread Flag: (1) stopFlag = false
Thread Flag: (2) stopFlag = false
Thread Flag: (3) stopFlag = false
Thread Flag: (4) stopFlag = false
Thread Flag: (5) stopFlag = false
main: stopping Thread Flag, set stopFlag = true
Thread Flag: (6) stopFlag = true, exit
----------------------------------------
Thread Interrupt: (1)
Thread Interrupt: (2)
Thread Interrupt: (3)
Thread Interrupt: (4)
Thread Interrupt: (5)
main: stopping Thread Interrupt, call interrupt()
Thread Interrupt: interrupted
Thread Interrupt: exit
----------------------------------------
Thread IsInterrupted: isInterrupted(1) = false
Thread IsInterrupted: isInterrupted(2) = false
Thread IsInterrupted: isInterrupted(3) = false
...
Thread IsInterrupted: isInterrupted(19) = false
Thread IsInterrupted: isInterrupted(20) = false
main: stopping Thread IsInterrupted, call interrupt()
Thread IsInterrupted: isInterrupted(21) = false
Thread IsInterrupted: isInterrupted(22) = true, exit
----------------------------------------
Thread Join 1
Thread Join 1: exit
Thread Join 2
Thread Join 2: exit
Thread Join 3
Thread Join 3: exit
2. 线程死锁
线程死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。
下面我们通过一些实例来说明死锁现象。
生活中的一个实例:两个人面对面过独木桥,甲和乙都已经在桥上走了一段距离,即占用了桥的资源,甲如果想通过独木桥的话,乙必须退出桥面让出桥的资源,让甲通过,但是乙不服,为什么让我先退出去,我还想先过去呢,于是就僵持不下,导致谁也过不了桥,这就是死锁。
1) 死锁产生的原因
(1) 系统资源的竞争
通常系统中拥有的不可剥夺资源,其数量不足以满足多个进程运行的需要,使得进程在运行过程中,会因争夺资源而陷入僵局,如磁带机、打印机等。只有对不可剥夺资源的竞争才可能产生死锁,对可剥夺资源的竞争是不会引起死锁的。
(2) 进程推进顺序非法
进程在运行过程中,请求和释放资源的顺序不当,也同样会导致死锁。例如,并发进程 P1、P2分别保持了资源R1、R2,而进程P1申请资源R2,进程P2申请资源R1时,两者都会因为所需资源被占用而阻塞。
2)死锁发生时的条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。独木桥每次只能通过一个人。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。乙不退出桥面,甲也不退出桥面。
(3) 不剥夺条件: 进程已获得的资源,在未使用完之前,不能强行剥夺。甲不能强制乙退出桥面,乙也不能强制甲退出桥面。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。如果乙不退出桥面,甲不能通过,甲不退出桥面,乙不能通过。
3) 如何避免死锁
在有些情况下死锁是可以避免的。下面介绍三种用于避免死锁的技术:
(1) 加锁顺序(线程按照一定的顺序加锁)
(2) 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
(3) 死锁检测
Java 中死锁最简单的情况是,一个线程T1持有锁L1并且申请获得锁L2,而另一个线程T2持有锁L2并且申请获得锁L1,因为默认的锁申请操作都是阻塞的,所以线程T1和T2永远被阻塞,导致死锁。
这是最容易理解也是最简单的死锁的形式。但是实际环境中的死锁往往比这个复杂的多。可能会有多个线程形成了一个死锁的环路,比如:线程T1持有锁L1并且申请获得锁L2,而线程T2持有锁L2并且申请获得锁L3,而线程T3持有锁L3并且申请获得锁L1,这样导致了一个锁依赖的环路:T1依赖T2的锁L2,T2依赖T3的锁L3,而T3依赖T1的锁L1,从而导致了死锁。
实例:
1 public class App {
2 public static void main( String[] args ) {
3
4 // 死锁的情况1
5 Thread t1 = new Thread(new DeadLock(true), "Thread DeadLock 1");
6 Thread t2 = new Thread(new DeadLock(false), "Thread DealLock 2");
7
8 // 死锁的情况2
9 //Thread t1 = new Thread(new DeadLock(false), "Thread DeadLock 1");
10 //Thread t2 = new Thread(new DeadLock(true), "Thread DealLock 2");
11
12 // 不死锁的情况1
13 //Thread t1 = new Thread(new DeadLock(true), "Thread DeadLock 1");
14 //Thread t2 = new Thread(new DeadLock(true), "Thread DealLock 2");
15
16 // 不死锁的情况2
17 //Thread t1 = new Thread(new DeadLock(false), "Thread DeadLock 1");
18 //Thread t2 = new Thread(new DeadLock(false), "Thread DealLock 2");
19
20 t1.start();
21 t2.start();
22
23 }
24 }
25
26 class DeadLock implements Runnable{
27
28 private static Object obj1 = new Object();
29 private static Object obj2 = new Object();
30 private boolean flag;
31
32 public DeadLock(boolean flag){
33 this.flag = flag;
34 }
35
36 @Override
37 public void run(){
38 String name = Thread.currentThread().getName();
39 System.out.println(name + ":running ...");
40
41 /*
42 * 同时开启线程1和线程2:
43 * 1. 如果让线程1执行 "代码1"(flag==true),让线程2执行 "代码2" (flag==false), 会死锁;
44 * 2. 如果让线程1执行 "代码2"(flag==false),让线程2执行 "代码1" (flag==true), 会死锁;
45 * 3. 如果让线程1执行 "代码1"(flag==true),让线程2执行 "代码1" (flag==true), 不会死锁;
46 * 4. 如果让线程1执行 "代码2"(flag==false),让线程2执行 "代码2" (flag==false), 不会死锁;
47 */
48 if (flag) {
49 // 代码1
50 synchronized(obj1){
51 System.out.println(name + ": lock obj1");
52 System.out.println(name + ": try to lock obj2 ...");
53 try {
54 Thread.sleep(1000);
55 } catch (InterruptedException e) {
56 e.printStackTrace();
57 }
58 synchronized(obj2){
59 // 死锁时执行不到这里
60 System.out.println(name +": after 1 second, lock obj2");
61 }
62 }
63 } else {
64 // 代码2
65 synchronized(obj2){
66 System.out.println(name + ": lock obj2");
67 System.out.println(name + ": try to lock obj1 ...");
68 try {
69 Thread.sleep(1000);
70 } catch (InterruptedException e) {
71 e.printStackTrace();
72 }
73 synchronized(obj1){
74 // 死锁时执行不到这里
75 System.out.println(name +": after 1 second, lock obj1");
76 }
77 }
78 }
79 }
80 }
输出:
Thread DeadLock 1:running ...
Thread DeadLock 1: lock obj1
Thread DeadLock 1: try to lock obj2 ...
Thread DealLock 2:running ...
Thread DealLock 2: lock obj2
Thread DealLock 2: try to lock obj1 ...
// 此时程序死锁