1. 線程終止
基於可見性的volatile實現
// 定義任務線程
class VolatileTask {
private volatile boolean flag = false;
public void read() {
while (!flag){
System.out.println("query data ....");
}
System.out.println("query data done ....");
}
public void write(){
flag = true;
System.out.println("writing data done ....");
}
}
// 執行main方法
final VolatileTask task = new VolatileTask();
new Thread(new Runnable() {
@Override
public void run() {
task.read();
}
}).start();
TimeUnit.SECONDS.sleep(2);
new Thread(new Runnable() {
@Override
public void run() {
task.write();
}
}).start();
- 執行結果:
// ...
query data ....
query data ....
query data ....
writing data done ....
query data done ....
- 分析
- 上述執行的結果屬於線程執行正常結束
- 上述是在client模式下執行,沒有執行重排序操作,在JMM規範中,volatile保持可見性,在字節碼層面,volatile帶有修飾符ACC_VOLATILE,在JVM規範中有聲明爲沒有緩存,也就是直接從主內存讀取數據,因此在另一個線程可以讀取到flag的最新值
stop方法以及存在的問題
class Task implements Runnable {
private int i = 0;
private int j = 0;
@Override
public void run() {
synchronized (this){
++i;
try{
TimeUnit.SECONDS.sleep(3);
}catch (InterruptedException e){
e.printStackTrace();
}
++j;
}
}
public void output(){
System.out.printf("time=%s,i=%d, j=%d%n", System.currentTimeMillis(), i, j);
}
}
// main方法
Task task = new Task();
Thread t1 = new Thread(task);
t1.start();
TimeUnit.SECONDS.sleep(1);
t1.stop();
task.output();
while (t1.isAlive()){
}
// 保證線程已經終止
task.output();
- 執行結果
// 線程未終止
time=1582099251750,i=1, j=0
// 線程已終止
time=1582099251769,i=1, j=0
- 分析
- 根據上述執行的結果,執行stop方法無法保證數據達到我們預期值,即j=1
- 其次也可以看出stop方法調用執行之後對線程是無法預測的,也就是說線程在執行的過程中突然收到停止的方法“蒙圈”了,無法捕獲到異常信息
- 最後一點就是在同步代碼中結束的時候,stop方法直接退出並沒有將同步代碼完全執行完
使用interrupt方法中斷線程
// task 代碼不變
// main方法
Task task = new Task();
Thread t1 = new Thread(task);
t1.start();
TimeUnit.SECONDS.sleep(1);
t1.interrupt();
task.output();
while (t1.isAlive()){
}
// 保證線程已經終止
task.output();
- 執行結果
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at com.xiaokunliu.homework.thread.base.methods.Task.run(ThreadStop.java:87)
at java.lang.Thread.run(Thread.java:748)
time=1582099560454,i=1, j=0
time=1582099560475,i=1, j=1
- 分析
- 可以看出,上述執行interrupt方法之後,會在線程中拋出中斷異常,線程可以進行捕獲處理
- 其次中斷操作能夠保證同步操作中的代碼能夠被執行,從代碼中也可以知道是因爲可以處理中斷異常,因此在實際開發過程中線程可以業務策略進行處理保證數據是正常可預期的
2. 線程join方法
join的場景
假設現在有一個業務場景是查詢直播信息,而查詢直播信息需要幾個步驟完成,一個是獲取用戶的頻道信息,二是查詢是否在推薦直播位置中,三是該直播是否正在直播,上述三個接口分別是在基礎組,應用組和直播組開發團隊進行維護,這個時候爲了完成需求,我需要通過接口的方式進行分別調用然後匯聚再返回,此時我們可以開三個線程,一個是處理用戶頻道查詢,一個是查詢是否在推薦位中,一個是查詢是否正在直播的狀態,於是有以下代碼(僞代碼)
// main方法
Thread t1 = new Thread(){
public void run(){
// get user channel
}
};
Thread t2 = new Thread(){
public void run(){
// get user recommand lives
}
};
Thread t3 = new Thread(){
public void run(){
// get user get live status
}
};
t1.start();
t2.start();
t3.start();
// 這裏是處理彙總的數據方式,因此必須等待上述執行完成
t1.join();
t2.join();
t3.join();
- 分析
- join方法就是在當前線程調用join方法的線程中的任務執行完成之後再進行下一步的操作
- 其次基於上述的應用場景,還可以使用CountDownLatch或者是Future/Callable抑或是join/fork框架實現
3. 線程的yeild方法
yeild定義與理解
yield是屬於一個由static native 修飾的底層實現機制,它的作用是一個“不完全讓出CPU資源的權利”來調度線程
現在有一個應用場景: 假設執行一個耗時且不重要的A任務需要10s,而執行一個緊急不耗時的B任務需要1s,那麼這個時候當啓動A線程的時候,在A線程中調用yield()方法來告訴CPU說我當前執行的任務不是很重要但是比較耗時,可以適當讓出CPU資源優先給其他緊急處理任務的線程執行
類比於一個生活場景就是去看醫生,醫生可能會在會診一個普通病人的時候突然遇到重症病人,需要緊急優先處理的場景
yeild示例代碼
Thread t1 = new Thread("線程1"){
@Override
public void run() {
try{
// 執行完成需要2s
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "執行重要的事情");
}catch (Exception e){
e.printStackTrace();
}
}
};
Thread t2 = new Thread("線程2"){
@Override
public void run() {
try{
// 讓給執行重要的線程優先執行,當前爲不重要且耗時操作
Thread.yield();
TimeUnit.SECONDS.sleep(12L);
System.out.println(Thread.currentThread().getName()+"執行不重要的事情。。。");
}catch (Exception e){
e.printStackTrace();
}
}
};
// 保證t2 先執行
t2.start();
TimeUnit.MILLISECONDS.sleep(500);
t1.start();
t2.join();
t1.join();
System.out.println("執行完成。。。");
- 執行結果如下
線程1執行重要的事情
線程2執行不重要的事情。。。
執行完成。。。