/
基础知识.md
257 lines (151 loc) · 8.34 KB
/
基础知识.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
### 基础
#### 线程状态
![在这里插入图片描述](https://img-blog.csdnimg.cn/2020101423393946.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzkzNDYwNw==,size_16,color_FFFFFF,t_70#pic_center)
**New**:这个状态呢,就是线程对象创建之后、启动之前,就是这个状态。
用代码来说呢,就是
```java
Thread t = new Thread();
t.getState(); // New
```
**Runnable**: 当调用start方法后呢,线程就会进入Runnable状态,表示,我这个线程可以被执行了,如果调度器给这个线程分配了CPU时间,那么这个线程就可以被执行,这里一定要正确区分一下Runnable不是说**正在执行**,而是**可以被执行**,这两个还是有区别的。
```java
Thread t = new Thread();
t.getState(); // New
t.start(); // Runnable
```
**Blocked**: 这个状态,当线程要进入临界区的时候,会发生。如果有一个线程已经到了获取锁进入了临界区,那么其他线程到了临界区会进入Blocked状态,由调度器选一个来执行,如果这个线程执行完毕后,大家还是一同为Blocked状态,调度器再选一个来执行。
```java
Thread t = new Thread(new Runnable {
void run() {
synchronized (lock) { // <3> Blocked
// dothings
}
}
});
t.getState(); // <1> New
t.start(); // <2> Runnable
```
**Waiting**: 当调用了wait,join方法后,就会进入这个状态。一旦进入到这个状态,CPU就不会管你了,直到有别的线程通过notify方法将它唤起,否则的话,就会一直在等待中。其主要作用是需要等待其他线程执行完,满足了一定条件才可以继续往下执行,所以不会浪费 cpu 资源。
```java
Thread t = new Thread(new Runnable {
void run() {
synchronized (lock) { // Blocked
// dothings
while (!condition) {
lock.wait(); // <3> Waiting
}
}
}
});
t.getState(); // <1> New
t.start(); // <2> Runnable
```
**Timed_Waiting**: 这个状态也是等待,但是是有一个计时器在里面,最常见的是使用 Thread.sleep 方法触发,触发后,线程就进入了 Timed_waiting 状态,随后会由计时器触发,再进入Runnable状态。
```java
Thread t = new Thread(new Runnable {
void run() {
Thread.sleep(1000); // Timed_waiting
}
});
t.getState(); // New
t.start(); // Runnable
```
**Terminated**: 终结状态,当线程的所有代码都被执行完毕后,会进入到这个状态,这个就是字面意思了。
#### API
**线程的新建**
```java
Thread t = new Thread();
```
**run()**
```java
Thread t = new Thread();
t.run();
```
不能使用上面的方式启动线程,这样调用不会产生任何新的线程,只是一个单纯的方法调用。要想开启线程,不能使用`run()`,而应该用`start()`。
**start()**
```java
new Thread(new Runnale() {
@Override
public void run() {}
}).start();
```
产生一个新线程去执行 run() 方法,只能调用一次,如果第二次调用,会抛出异常
**线程的终止**
`stop()`方法,但是已经被**废弃**。该方法太过暴力,强行把执行到一半的线程终止掉,可能会引起数据不一致的问题。`stop()`方法会直接终止线程,并**立即释放这个线程持有的锁**,而锁恰好是用来维持对象的一致性的。
那么要如何终止线程呢?可以自定义退出线程的方法,比如设置标志位
```java
volatile boolean stoped;
public void myStop() {
stoped = true;
}
```
在线程**合适的地方**, 通过判断`stoped`变量来手动退出线程。
```java
@Override
public void run() {
while (true) {
if (stoped) {
break;
}
}
// synchronized (object) {do sth.}
}
```
**线程的中断**
线程中断不会使线程立即退出,而是**通知**目标线程“你应该退出了”,退不退出是由目标线程来决定的。
设置中断
* Thread类的实例通过 `interrupt()` 方法,它通知目标线程中断,也就是**设置中断标志位**。
检查是否中断(但同时会**清除当前线程的中断标志位** )
* Thread类的实例通过 `isInterrupt()` 方法,用于判断当前线程是否被中断(通过检查中断标志位);
* 静态方法 `Thread.interrupted()` 也可以判断当前线程的中断状态
**wait() 和 notify()**
`wait()` 是 Object的方法,也就是说每个对象都能调用。在当前线程中某个对象调用了 `wait()` 方法则该线程就停止执行,转为等待状态,并加入对象 obj 的等待队列,一直等到其他线程调用了`obj.notify()`,才从等待队列移出,重新尝试获取锁。
Object类还有一个`notifyAll()`方法,会唤醒某对象等待队列中的所有线程。
`notify()` 和 `wait()`方法在**执行前必须获得对象的锁**。为此在`synchronized (obj)`中先获得了锁,才能调用`wait()`或者`notify()`方法。
**线程的挂起和继续执行**
Thread类中还有两个**已经被废弃**的方法,分别是`suspend()`和`resume()`。
`suspend()`在导致线程暂停的同时,**不会释放任何锁**,而且**线程状态还是Runnable**,其他任何线程都得不到被该线程占用的锁,直到在线程上执行了`resume()`,被挂起的线程才能继续,其他阻塞在相关锁上的线程也才可以继续执行。
但是如如`resume()`不小心在`suspend()`之前调用了,可能造成线程被**永久挂起**,从而永久占用锁。
注意和`notify()`、`wait()`方法的区别。由于方法已被废弃,不再讨论更多。
**join()**
`join()`表示将线程“加入”到当前线程中,会一直阻塞当前线程,直到该线程执行完毕,或者说:当前线程一直等待join入的线程执行完毕后,才能继续执行。
```java
public class Abc {
public static volatile int i;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {for (i = 0; i < 100; i++);});
t.start();
t.join();
System.out.println(i);
}
}
```
举个例子,上面的代码中有main线程和t线程,由于线程 t 的 join ,使得 main 线程中`System.out.println(i)`的一定是等待t线程执行完毕后才能执行,因此打印值一定是100 .
`join() `的实现依赖于 `wait()`。**A线程中调用了B线程的 join 方法,则相当于A线程调用了B线程的 wait 方法,在调用了B线程的 wait 方法后,A线程就会进入阻塞状态**。所以 A 线程会等待 B 执行完毕后才能继续执行。
**yield()**
`yield()`表示主动礼让当前线程的CPU使用权,让出不是会不会回来参与CPU资源的争夺,下次还能被分配到。调用该方法,好比是当前线程说:我的工作暂时做完,给其他线程一些工作机会。
#### 其他概念
##### 守护线程
守护线程用于守护用户进程。守护进程在后台默默完成一些系统性的服务,比如垃圾回收线程、JIT 线程就可理解成守护线程。
而用户线程是用于完成程序的业务操作的,**当程序中的所有用户线程执行完毕,守护线程没有了可守护的对象,程序自然就退出**。
即:在一个Java程序中只有守护线程时,虚拟机就自然退出。
```java
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true) {
}
});
t.setDaemon(true);
t.start();
System.out.println("over");
}
```
在上面的代码中,线程 t 被设置成守护线程(必须在`start()`之前调用),用户线程 main 在打印 "over" 后执行完毕,t 没有要守护的线程了,所以程序会出,如果不设置t为守护线程,我们知道由于`while (true)`程序永远不会退出。
##### 线程优先级
Java 的线程有 **优先级**,高优先级线程在竞争资源时更有优势,但这也是个**概率问题**,高优先级也可能抢占失败,低优先级线程也不是说一定会饥饿。
Java中的线程有1~10的优先级,数字越大,优先级越高。Thread类中内置了三个默认的优先级,如下。
```java
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
```