前言
今天小鹹兒來講解一個好玩的事,那就是線程之間該如何通信,線程通信之後又會出現什麼問題?
敘述
寶圖
先來一張導圖來看看線程通訊的分佈?
Join 線程
疑問:如果想要線程按照用戶自定義的順序執行的話,那該如何操作呢?
思考:如果能夠讓線程等待先執行的線程執行完,再執行不就能達到效果了嗎!?
果然出現問題之後,就會有對應的解決之法,接下來先看一個簡單而且有趣的方法——join
Thread 提供了讓一個線程等待另一個線程完成的方法——join()方法。
代碼展示:
package com.practice.demo.thread;
/**
* 線程的第三個實例,繼承Thread類
* 測試join()方法
* @author Phyllis
* @date 2019年7月2日17:26:23
*/
public class JoinThread extends Thread{
/**
* 提供一個有參數的構造器,用於設置該線程的名字
* @param name 線程的名字
*/
public JoinThread(String name){
super(name);
}
/**
* 重寫run()方法,定義線程執行體
*/
@Override
public void run() {
for (int i = 0; i<100; i++){
System.out.println(getName()+" "+ i);
}
}
public static void main(String[] args) throws Exception{
// 啓動子線程
new JoinThread("新線程").start();
for (int i = 0; i<100; i++){
if (i == 20){
JoinThread jt = new JoinThread("被Join的線程");
jt.start();
// main線程調用了jt線程的join()方法,main線程
// 必須等待jt執行結束纔會向下執行
jt.join();
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
打印結果:
結果: 當i=20時,被join的線程開始執行,這時,main線程一直處於阻塞的狀態,所以一直在等待,等到join的線程結束後,則開始執行。
sleep
這個方法看到名字就會很清楚是幹什麼用的了,就是表面意思,讓線程睡一會。
synchronized (res){
try {
if (!res.flag){
res.wait();
}
Thread.sleep(1000);
}catch (Exception e){
}
System.out.println(res.name + "," + res.sex);
res.flag = false;
res.notify();
}
如果需要讓當前正在執行的線程暫停一段時間,並進入阻塞狀態,則可以通過Thread類的靜態sleep()方法來實現。
與之類似的方法還有一個yield()方法
yield
yield()靜態方法和sleep()方法類似,它也可以讓當前正在執行的線程暫停,但它不會阻塞該線程,它只是將該線程轉入就緒狀態。
synchronized (res){
try {
if (!res.flag){
res.wait();
}
Thread.yield();
}catch (Exception e){
}
System.out.println(res.name + "," + res.sex);
res.flag = false;
res.notify();
}
缺點: yield方法只是讓當前線程暫停一下,讓系統的線程調度器重新調度一次,完全可能的情況是:當某個線程調用了yield方法暫停之後,線程調度器又再次調度到了該線程。
sleep方法和yield方法的區別:
- sleep方法暫停當前線程後,會給其他線程執行的機會,不會理會其他線程的優先級;但是yield方法只會給優先級相同或者更高級別的線程機會。(看來yield還看高低區分的哼)
- sleep方法會將線程轉入阻塞狀態,直到經過阻塞時間纔會轉入就緒狀態;而yield不會將線程轉入阻塞狀態,它只是強制當前線程進入就緒狀態。因此有可能在暫停後,因獲得處理器資源而再次執行。
- sleep方法聲明拋出了InterruptException異常,所以調用sleep方法時要麼捕捉該異常,要不顯示聲明拋出該異常;而yield方法則沒有聲明拋出任何異常。
- sleep方法比yield方法有更好的可移植性。
等待喚醒機制
重點來了,等待喚醒機制是什麼,在多線程執行過程中,在A線程對共享變量,進行操作時,B線程要等待。A線程結束操作時,則喚醒B線程
示例:生產與消費
package com.practice.demo.thread;
/**
* 共享對象
* @author Phyllis
* @date 2019年7月12日09:15:26
*/
class Res{
/**
* 姓名
*/
public String name;
/**
* 性別
*/
public String sex;
/**
* 爲true的情況下,允許讀,不能寫
* 爲false的情況下,允許寫,不能讀
*/
public boolean flag = false;
}
/**
* 生產這線程
* @author Phyllis
* @date 2019年7月12日09:16:55
*/
class InThread extends Thread{
public Res res;
public InThread(Res res){
this.res = res;
}
@Override
public void run() {
// count 爲0 或者 1
int count = 0;
while (true){
synchronized (res){
if (res.flag){
try {
// 釋放當前鎖對象,當前線程等待
res.wait();
}catch (Exception e){
}
}
if (count == 0){
res.name = "小紅";
res.sex = "女";
} else{
res.name = "小軍";
res.sex = "男";
}
// 0 1 0 1 0 1 0 1
count = (count + 1) % 2;
// 標記當前線程爲等待
res.flag = true;
// 喚醒被等待的線程
res.notify();
}
}
}
}
/**
* 消費這線程,讀
* @author Phyllis
* @date 2019年7月12日09:16:55
*/
class OutThread extends Thread {
public Res res;
public OutThread(Res res){
this.res = res;
}
@Override
public void run() {
while (true){
synchronized (res){
try {
if (!res.flag){
res.wait();
}
Thread.sleep(1000);
}catch (Exception e){
}
System.out.println(res.name + "," + res.sex);
res.flag = false;
res.notify();
}
}
}
}
/**
* 客戶端
* @author Phyllis
* @date 2019年7月12日09:16:55
*/
public class ThirdThread{
public synchronized static void main(String[] args) throws InterruptedException {
Res res = new Res();
InThread inThread = new InThread(res);
OutThread outThread = new OutThread(res);
inThread.start();
outThread.start();
}
}
打印結果:
所以使用等待喚醒機制來保證生產一個,消費一個。
注意: wait和notify方法,一定要在synchronized中進行,持有同一把鎖。
區別
wait和join的區別:
wait需要被喚醒。
wait和sleep的區別:
sleep不會釋放鎖。
問題
在寶圖中也可以明顯的看出,小鹹兒在通訊那裏打了兩個問號,也就是說通訊有可能會導致線程安全的問題,那麼線程安全是什麼呢?又該如何解決呢?且聽小鹹兒下次分享。
總結
多線程是十分實用並且常用的內容,接下來小鹹兒還會繼續深入學習多線程,更多的內容等待更新。
感謝您的閱讀~~