1. 程式人生 > 其它 >多執行緒二——常用方法、生命週期、互斥鎖、死鎖與釋放鎖

多執行緒二——常用方法、生命週期、互斥鎖、死鎖與釋放鎖

執行緒的終止

1、當執行緒完成任務後,就會自動退出

2、還可以通過使用變數來控制run方法退出的方式停止執行緒。

案例:

啟動一個執行緒,然後在main執行緒中去停止子執行緒(**我們直線在子執行緒上設定一個變數,直接讓迴圈開關關閉 **)

/**
 * @author 喂S別鬧
 * @create 2021/11/10-8:59
 * @Version 1.0
 * @Description: 執行緒的退出
 */
public class ThreadExit {
    public static void main(String[] args) {
        T t = new T();
        t.start();

        //如果希望主執行緒去控制t執行緒的終止,那麼我們就需要去修改loop變數
        //讓t退出run方法,從而 終止t執行緒的迴圈

        //讓主執行緒休眠10S,再通知退出
        try {
            System.out.println("主執行緒休眠10S");
            Thread.sleep(10 * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.setLoop(false);

    }

}

class T extends Thread {
    int count = 0;
    //設定一個控制變數
    private boolean loop = true;

    @Override
    public void run() {
        while (loop) {
            try {
                Thread.sleep(50);//休眠50ms
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("thread...執行中" + ++count);
        }
    }

    public void setLoop(boolean loop) {
        this.loop = loop;
    }
}

執行緒的常用方法

第一組常規方法以及Interrupt的用法

  • setName : 設定執行緒的名稱
  • getName
  • start:使該執行緒開始執行;Java虛擬機器底層呼叫該執行緒的start()方法
  • run:呼叫執行緒物件的run方法
  • setPriority :更改執行緒的優先順序
  • getPriority:獲取執行緒的優先順序
  • sleep:在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行)
  • interrupt:中斷執行緒

注意細節:

1、start底層會建立新的執行緒,呼叫run,run就是一個簡單的方法呼叫,不會啟動新的執行緒

2、執行緒優先順序的範圍

3、interrupt,中斷執行緒,但並沒有真正的結束執行緒。所以一般用於中斷正在休眠的執行緒

4、sleep:執行緒的靜態方法,使當前執行緒休眠

Interrupt的用法

/**
 * @author 喂S別鬧
 * @create 2021/11/17-11:30
 * @Version 1.0
 * @Description: Interrup中斷執行緒的使用
 */
public class ThreadInterrupt {
    public static void main(String[] args) throws InterruptedException {
        T1 t1 = new T1();
        t1.setName("喂S別鬧");
        t1.setPriority(Thread.MIN_PRIORITY);
        t1.start();
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("主執行緒" + i);
        }
        System.out.println(t1.getName() + "執行緒的優先順序是:" + t1.getPriority());
        t1.interrupt(); //當執行到這裡的時候就會中斷T1執行緒的休眠
    }
}

class T1 extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "執行緒執行..." + i);
            }
            try {
                System.out.println(Thread.currentThread().getName() + "休眠~~");
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                //當該執行緒執行到一個interrupt方法時,就會catch一個異常,可以加入自己的業務程式碼
                //InterruptedException 是捕獲到一箇中斷異常
                e.printStackTrace();
            }
        }
    }
}

常用方法第二組yield與join

1、yield:執行緒的禮讓。讓出 CPU,讓其他執行緒去執行,但禮讓的時間不確定,所有也不一定禮讓成功。(t1執行緒和t2執行緒,如果t1執行緒執行yield,那麼就是t1執行緒主動禮讓t2執行緒先執行,但是如果CPU執行得快,有足夠時間,那麼禮讓就會不成功

2、Join;執行緒的插隊。插隊的執行緒一旦插入成功,則肯定會先執行插入的執行緒所有任務(比如有t1和t2兩個執行緒在交替執行,如果在t1執行緒中執行t2.join()方法,那麼CPU就會直接會把t2執行緒完,然後再執行t1

例子練習

題目:

1、主執行緒每隔1s,輸出hi,一共10次

2、當輸出5次hi的時候,啟動一個子執行緒(要求實現Runnable),每隔1s輸出Hello,等該執行緒輸出10次hello後退出

3、主執行緒繼續輸出hi,直到主執行緒退出

比如:

hi1
hi2
hi3
hi4
hi5
子執行緒結束
hello1
hello2
。。。
hello10
hi6
..
hi10
主執行緒結束

程式碼:

/**
 * @author 喂S別鬧
 * @create 2021/11/17-14:17
 * @Version 1.0
 * @Description: 主執行緒和子執行緒交替使用的練習
 * 1、主執行緒每隔1s,輸出hi,一共10次
 * <p>
 * 2、當輸出5次hi的時候,啟動一個子執行緒(要求實現Runnable),每隔1s輸出Hello,等該執行緒輸出10次hello後退出
 * <p>
 * 3、主執行緒繼續輸出hi,直到主執行緒退出
 */
public class ThreadMethodExercise {
    public static void main(String[] args) throws InterruptedException {
      /*  T3 t3 = new T3();
        Thread t4 = new Thread(t3);*/
        Thread t3 = new Thread(new T3());


        for (int i = 1; i <= 10; i++) {
            System.out.println("Hi" + i);
            if (i == 5) {//說明主執行緒輸出了5次
                t3.start(); //啟動子執行緒 輸出Hello
                t3.join(); //立即將t3子執行緒,插入到main執行緒,讓t3先執行
            }
        }
    }
}

class T3 implements Runnable {
    private int count = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println("Hello" + ++count);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 10) {
                break;
            }
        }
    }
}

常用方法:使用者執行緒和守護執行緒

1、使用者執行緒:也叫工程執行緒,當執行緒的任務執行完或通知方式結束

2、守護執行緒:一般是為工作執行緒服務的,當所有的使用者執行緒結束,守護執行緒就自動結束。

3、常見的守護執行緒:垃圾回收機制

案例:測試如何將一個執行緒設定成守護執行緒

/**
 * @author 喂S別鬧
 * @create 2021/11/17-15:38
 * @Version 1.0
 * @Description: 將一個執行緒設定成為守護執行緒
 */
public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        //如果希望當主執行緒結束之後,子執行緒就會自動結束
        //只需將子執行緒設為守護執行緒即可
        myDaemonThread.setDaemon(true);
        myDaemonThread.start();
        for (int i = 1; i <= 10; i++) {
            System.out.println("主執行緒");
            Thread.sleep(1000);
        }
    }
}

class MyDaemonThread extends Thread {
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("哈哈哈哈");
        }
    }
}

執行緒的生命週期(中間有個圖很重要)

JDK中用Thread.State列舉表示了執行緒的幾種狀態

在官方文件裡,是有6種執行緒狀態的

1、new:尚未開啟執行緒處於此狀態

2、Runnable :在Java虛擬機器中執行的執行緒處於此狀態(注意runnable狀態不是正在執行狀態 ,只是表示可以運行了,具體執行還需要看執行緒排程器來控制)

3、Blocked:被阻塞等待監視器鎖定的執行緒處於此狀態

4、waiting:正在等待另一個執行緒執行特定動作的執行緒處於此狀態

5、Time_waiting:正在等待另一個執行緒執行動作達到指定等待時間的執行緒處於此狀態(超時等待

6、Terminated:已退出的執行緒處於此狀態

圖很重要

例子:

/**
 * @author 喂S別鬧
 * @create 2021/11/17-16:18
 * @Version 1.0
 * @Description: 執行緒的狀態展示
 */
public class ThreadState {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        System.out.println(t.getName() + "狀態" + t.getState());
        t.start();

        while (Thread.State.TERMINATED != t.getState()) {
            System.out.println(t.getName() + "狀態" + t.getState());
            Thread.sleep(500);
        }

        System.out.println(t.getName() + "狀態" + t.getState());
    }
}

class T extends Thread {
    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 10; i++) {
                System.out.println("hi " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            break;
        }
    }
}

Synchronized關鍵詞

執行緒同步機制

1、在多執行緒程式設計中,一些敏感資料不允許被多個執行緒同時訪問,此時就使用同步訪問技術,保證資料在任何同一時刻,最多有一個執行緒訪問,以保證資料的完整性。

2、簡單說:執行緒同步,即當有一個執行緒在對記憶體進行操作時,其他執行緒都不可以對這個記憶體地址程序操作,直到該執行緒完成操作,其他執行緒才能對該記憶體地址進行操作。

同步具體方法--Synchronized

1、同步程式碼塊
synchronized(物件){//得到物件的鎖,才能操作同步程式碼
//需要被同步程式碼;
}

2、synchronized還可以放在方法宣告中,表示整個方法-為同步方法
    public synchronized void m(String name){
    //需要被同步的程式碼
}

例子

public class SellTicket {
    public static void main(String[] args) { 	
        //測試同步
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start(); //第一個執行緒
        new Thread(sellTicket03).start(); //第2個執行緒
        new Thread(sellTicket03).start(); //第3個執行緒
    }
}

//實現runnable介面,使用synchronized實現執行緒同步
class SellTicket03 implements Runnable {

    private int Ticketnum = 100; //讓多個執行緒共享
    private boolean loop = true;

    public synchronized  void sell(){ //同步方法,在同一時刻,只能有一個執行緒來執行run方法
        if (Ticketnum <= 0) {
            System.out.println("售票結束了");
            loop=false;
            return;
        }
        //休眠
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("視窗 " + Thread.currentThread().getName() + " 賣出了一張票 " + "還剩" + (--Ticketnum) + "張票");

    }

    @Override
    public void run() {
        while (loop) {
            sell();
        }
    }
}

分析同步原理

互斥鎖

基本介紹:

1、Java中,引入了物件互斥鎖的概念,來保證共享資料操作的完整性

2、每個物件都對應於一個可稱為“互斥鎖”的標記,這個標記用來保證在任一時刻,只能有一個執行緒訪問該物件

3、關鍵字synchronized來與物件的互斥鎖聯絡。當某個物件用synchronized修飾時,表明該物件在任一時刻只能由一個執行緒訪問

4、同步的侷限性:導致程式的執行效率要降低

5、同步方法(非靜態的類)的鎖可以是this,也可以是其他物件(但必須是同一物件

6、同步方法(靜態的類)的鎖為當前類本身

使用互斥鎖來解決售票問題

可以在程式碼塊上加鎖

public class SellTicket {
    public static void main(String[] args) {

        //測試同步
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start(); //第一個執行緒
        new Thread(sellTicket03).start(); //第2個執行緒
        new Thread(sellTicket03).start(); //第3個執行緒
    }
}

//實現runnable介面,使用synchronized實現執行緒同步
class SellTicket03 implements Runnable {

    private int Ticketnum = 100; //讓多個執行緒共享
    private boolean loop = true;
    Object object = new Object();

    public /*synchronized*/ void sell() { //同步方法,在同一時刻,只能有一個執行緒來執行run方法
        synchronized (/*this*/ object) {
            if (Ticketnum <= 0) {
                System.out.println("售票結束了");
                loop = false;
                return;
            }
            //休眠
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("視窗 " + Thread.currentThread().getName() + " 賣出了一張票 " + "還剩" + (--Ticketnum) + "張票");

        }
    }

    @Override
    public  void run() {

        while (loop) {
            sell();
        }
    }
}

靜態類方法

public class SellTicket {
    public static void main(String[] args) {
        //測試同步
        SellTicket03 sellTicket03 = new SellTicket03();
        new Thread(sellTicket03).start(); //第一個執行緒
        new Thread(sellTicket03).start(); //第2個執行緒
        new Thread(sellTicket03).start(); //第3個執行緒
    }
}

//實現runnable介面,使用synchronized實現執行緒同步
class SellTicket03 implements Runnable {

    private int Ticketnum = 100; //讓多個執行緒共享
    private boolean loop = true;

    //同步方法靜態的類的鎖為當前類的本身
    //1、 public synchronized static void m1(){} 鎖是在SellTicket03.class
//    2、如果在靜態方法中,實現同步程式碼塊,就是類名.class
//    public static void m2(){
//        synchronized (SellTicket03.class){
//            System.out.println("1");
//        }
//    }
    public synchronized static void m1(){

    }
    public static void m2(){
        synchronized (SellTicket03.class){
            System.out.println("1");
        }
    }

互斥鎖注意事項

1、同步方法如果沒有使用static修飾:預設鎖物件為this

2、如果方法使用了static修飾,預設鎖物件:當前類.class

3、實現:

  • 需要先分析上鎖業務程式碼
  • 選擇同步程式碼塊還是同步方法
  • 要求多個執行緒的鎖物件為同一個即可。

執行緒的死鎖

介紹

多個執行緒都佔用了對方的鎖資源,但不肯相互讓步,導致了死鎖,在平時程式設計時是我們一定要避免的。

釋放鎖

釋放鎖的操作

1、當前執行緒的同步方法、同步程式碼塊執行結束

2、當前執行緒在同步程式碼塊、同步方法中遇到break、return

3、當前執行緒在同步程式碼塊、同步方法中出現了未處理的Error或Exception,導致異常結束

4、當前執行緒在同步程式碼塊、同步方法中執行了執行緒物件的wait()方法,當前執行緒暫停,並釋放鎖

不會釋放鎖的操作

1、執行緒執行同步程式碼塊或同步方法時,程式呼叫Thread.sleep()、Thread.yield()方法暫停當前執行緒的執行,不會釋放鎖。

2、執行緒執行同步程式碼塊時,其他執行緒呼叫了該執行緒的suspend()方法將該執行緒掛起,該執行緒不會釋放鎖。

注意:儘量避免用suspend()和resume()來控制執行緒,方法不再推薦使用