1. 程式人生 > 其它 >JAVA 程序執行緒詳解

JAVA 程序執行緒詳解

目錄

執行緒和程序

一、程序

  1. 程序是指執行中的程式,比如我們使用QQ,就啟動該程序分配記憶體空間.
  2. 程序是程式的一次執行過程,或是正在執行的一個程式。是一個動態的過程:有它自升的產生,存在和消亡的過程

二、執行緒

  1. 執行緒是由程序建立的,是程序的一個實體
  2. 一個程序可以擁有多個執行緒
    • 一個想執行緒還可以建立它的子執行緒

三、其他概念

  1. 單執行緒:同時允許執行一個執行緒

  2. 多執行緒:同一個時刻,可以執行多個執行緒

    • 比如:QQ可以開啟多個聊天視窗,一個迅雷程序,可以同時下載多個檔案
  3. 併發:同一個時刻,多個任務交替執行,造成"貌似同時"的錯覺,簡單的說,單核CPU實現的多工就是併發

  4. 並行:同一個時刻,多個任務同時執行。多核CPU可以同時執行

    • 也可能出現:並行和併發,並存在的情況

四、執行緒的基本使用

在Java中執行緒來使用有兩種方法

  1. 基礎Thread類,重寫run方法

  2. 實現Runnable介面,重寫run方法

案例一

![image-20220327105831422](!

)

package com.hspedu.threaduse;

/**
 * @author DL5O
 * @version 1.0
 * 通過繼承Thread 類建立執行緒
 */

public class Thread01 {
    public static void main(String[] args) throws InterruptedException {
        //建立Cat物件,可以當做執行緒使用
        Cat cat = new Cat();
        //啟動執行緒,呼叫start的時候還呼叫run方法
        cat.start();//最終執行 -> cat的run方法
        //cat.run();
        //如果這樣寫,run方法就是一個普通的方法,
        // 是由主執行緒呼叫了,並沒有真正的開執行緒,
        // 會阻塞在這裡,執行完畢後才會繼續執行下面的程式碼


        //說明:當main執行緒啟動一個子執行緒thread-0,主執行緒不會阻塞,
        // 即不會等待cat.start執行完畢後再往下執行
        //主執行緒中如果後面還有程式碼的話,還會繼續執行
        //這時我們的主執行緒和主執行緒 是交替執行的
        System.out.println("繼續執行~~,"+Thread.currentThread().getName());
        for (int i = 0; i < 60; i++) {
            System.out.println("主執行緒 i=" + i);
            //休眠1秒
            Thread. sleep(1000);
        }
    }
}

//1.當一個類繼承了Thread 類,該類就可以當做一個執行緒使用
//2.我們會重寫run方法,寫上自己的業務邏輯
//3.run方法在Thread也是實現了Runnable 介面的run方法
class Cat extends Thread {
    int times = 0;

    @Override
    public void run() {//重寫run方法,寫上自己的業務邏輯
        while (true) {
            //每隔一秒鐘,輸出"喵喵,我是小貓咪"
            System.out.println("喵喵,我是小貓咪" + (++times)
                    + ",執行緒名稱=" + Thread.currentThread().getName());
            //讓執行緒休眠1秒鐘
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (times == 80) {
                break;//當times 等於80就退出這個while迴圈,這時執行緒就退出了
            }
        }
    }
}
  1. 當執行這個程式程式碼的時候,會建立一個程序並給他分配空間
  2. 會先執行main方法,開啟一個main執行緒
  3. 當執行到car.start() 方法時會開啟一個新的執行緒,這個執行緒是mian執行緒的子執行緒
    • 注意:
      • 這個car.start()是非阻塞的,即不會等到這個方法執行完後才繼續往下執行
      • car.start()執行緒開啟後,會去呼叫run方法!!
        • 但是如果直接寫car.run(),這個時候則是用的主執行緒main執行緒去呼叫的,並沒有開啟新執行緒,執行玩這個方法後才會往下繼續執行

執行過程

(1)
public synchronized void start() {
    start0();
}
(2)
//start0() 是本地方法,是JVM呼叫,底層是c/c++實現的
//真正實現多執行緒的是start0(),而不是run方法
 private native void start0();

案例二

實現runnable介面

說明:

  1. java是單繼承的,在某些情況下已經繼承了某個父類,這時繼承Thread類方法來建立執行緒,顯示是不可能的
  2. java設計者們提供了另外一個方式建立執行緒,就是通過實現Runnalb介面來建立執行緒
package com.hspedu.threaduse;

/**
 * @author DL5O
 * @version 1.0
 * 通過實現介面Runnable 來開發執行緒
 */
public class Thread02 {
    public static void main(String[] args){
        Dog dog = new Dog();
        //dog.start(); 這裡不能呼叫start
        //建立了Thread物件,把 dog物件,實現了Runnable,放入thread
        Thread thread = new Thread(dog);
        thread.start();

        /*Tiger tiger = new Tiger();//實現了runnable介面
        ThreadProxy threadProxy = new ThreadProxy(tiger);
        threadProxy.start();
        System.out.println("哈哈哈哈");*/
        System.out.println("哈哈哈");
    }
}

class Animal{}

class Tiger extends Animal implements Runnable{

    @Override
    public void run() {
        System.out.println("老虎嗷嗷叫...");
    }
}

//模擬了最簡的Thread類
class ThreadProxy implements Runnable{//可以當做ThreadProxy ,執行緒代理

    private Runnable target = null;//屬性,型別是Runnable

    @Override
    public void run() {
        if(target != null){
            target.run();//進行動態繫結 執行型別是tiger,即實現了Runnable的類
        }
    }

    public ThreadProxy(Runnable target) {
        this.target = target;
    }

    public void start(){
        start0();//這個方法是真正實現多執行緒的方法
    }

    private void start0() {
        run();
    }

}

class Dog implements Runnable{

    int count = 0;
    @Override
    public void run() {//普通方法,並沒有啟動執行緒
        while(true){
            System.out.println("小狗汪汪叫..hi" + (++count) + Thread.currentThread().getName());
            //休眠一秒
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if(count == 10){
                break;
            }
        }

    }
}
  • 把實現了runnable介面的物件傳入到thread類,進行靜態代理,呼叫start方法時,會去呼叫start0方法,start0方法還會呼叫對應該類的run方法,run方法又會去呼叫傳入的物件的run方法,進行動態繫結

案例三

package com.hspedu.threaduse;

/**
 * @author DL5O
 * @version 1.0
 * main 執行緒啟動兩個子執行緒
 */
public class Thread03 {
    public static void main(String[] args) {
        T1 t1 = new T1();
        T2 t2 = new T2();
        Thread thread1 = new Thread(t1);//建立t1執行緒,用於輸出hello,world
        Thread thread2 = new Thread(t2);//建立t2執行緒,用於輸出hi

        thread1.start();
        thread2.start();

    }
}

class T1 implements Runnable {
    int count = 0;

    @Override
    public void run() {
        while (true) {
            System.out.println("hello,world " + (++count) + " "+Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (count == 10) {
                break;
            }
        }
    }
}

class T2 implements Runnable {
    int count = 0;
    @Override
    public void run() {
        while (true) {
            System.out.println("hi " + (++count) + " " + Thread.currentThread().getName());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 5) {
                break;
            }
        }
    }
}
  • 若還有執行緒沒有執行完畢,那麼主程序就不會退出

五、繼承Thread vs 實現Runnable有什麼區別

  1. 從java的設計來看,通過繼承Thread或者實現Runnable介面來建立執行緒本
    質上沒有區別,從jdk幫助文件我們可以看到Thread類本身就實現了Runnable介面
  2. 實現Runnable介面方式更加適合多個執行緒共享一個資源的情況,並且避免了
    單繼承的限制,建議使用Runnable
package com.hspedu.ticket;

/**
 * @author DL5O
 * @version 1.0
 * 使用多執行緒,模擬三個視窗同時售票
 * 總票數 100 張
 */
public class SellTicket {
    public static void main(String[] args) {
        //第一種方式
        //測試
        /*SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();

        sellTicket01.start();
        sellTicket02.start();
        sellTicket03.start();*/

        System.out.println("===使用介面的方式來售票===");
        SellTicket02 sellTicket02 = new SellTicket02();
        Thread thread1 = new Thread(sellTicket02);
        Thread thread2 = new Thread(sellTicket02);
        Thread thread3 = new Thread(sellTicket02);
        thread1.start();//第一個執行緒-視窗
        thread2.start();//第二個執行緒-視窗
        thread3.start();//第三個執行緒-視窗
    }
}


//使用第一種繼承thread的方式
class SellTicket01 extends Thread{
    private static int ticketNum = 100;//讓多個執行緒共享 ticketNum
    @Override
    public void run() {
        while (true){
            if (ticketNum <= 0){
                System.out.println("售票結束..");
                break;
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("視窗 " + Thread.currentThread().getName()
                    + " 售出一張票" + "剩餘票數=" + (--ticketNum));
        }
    }
}

//實現介面的方式
class SellTicket02 implements Runnable{
    private int ticketNum = 100;

    @Override
    public void run() {
        while (true){
            if (ticketNum <= 0){
                System.out.println("售票結束..");
                break;
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("視窗 " + Thread.currentThread().getName()
                    + " 售出一張票" + "剩餘票數=" + (--ticketNum));
        }
    }
}
  • 會出現超賣,和票數對不上的情況,這時候程式就會存在很大的問題=> 引出互斥等概念

六、執行緒的退出

基本說明:

  1. 當執行緒完成任務後,會自動退出
  2. 還可以通過變數來控制run方法退出的方式停止執行緒,即通知方式
package com.hspedu.exit_;

/**
 * @author DL5O
 * @version 1.0
 */
public class ThreadExitTest_ {
    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        test.start();

        Thread.sleep(10*1000);
        test.setLoop(false);
    }
}
class Test extends Thread{
    private boolean loop = true;
    private int cnt = 0;
    @Override
    public void run() {
        while(loop){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Test 執行中" + (++cnt));
        }
        System.out.println("Test 執行結束");
    }

    public boolean isLoop() {
        return loop;
    }

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

    public int getCnt() {
        return cnt;
    }

    public void setCnt(int cnt) {
        this.cnt = cnt;
    }
}

七、執行緒常用方法

常用方法第一組

  1. setName:給執行緒設定名稱
  2. getName:返回執行緒的名稱
  3. start:使執行緒開始執行
  4. run:執行緒呼叫的run方法
  5. setPriority:設定執行緒的優先順序
  6. getPriority:獲取執行緒的優先順序
  7. sleep:讓正在執行的執行緒休眠幾秒
  8. interrupt:中斷執行緒
  1. start底層會建立新的執行緒,會呼叫run,run本身不會啟動新執行緒,不會啟動新執行緒
  2. 執行緒優先順序的範圍
  3. interrupt,中斷執行緒,但並沒有真正結束執行緒。所以一般用於中斷正在休眠執行緒
  4. sleep:執行緒的靜態方法,使當前執行緒休眠

案例:

ThreadMethod01

package com.hspedu.method;

/**
 * @author DL5O
 * @version 1.0
 */
public class ThreadMethod01 {
    public static void main(String[] args) throws InterruptedException {
        T t = new T();
        t.setName("大龍");
        t.setPriority(Thread.MIN_PRIORITY);//設定最小的優先順序
        t.start();//啟動子執行緒

        //主執行緒列印5 個hi, 然後我就中斷 子執行緒的休眠
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
            System.out.println("hi " + (i+1));
        }

        System.out.println(t.getName() + " 執行緒的優先順序 = " + t.getPriority());//1
        t.interrupt();//中斷該執行緒,當執行到這裡,就會中斷,t執行緒的休眠

    }
}

class T extends Thread {//自定義的執行緒類

    @Override
    public void run() {
        while (true) {
            for (int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + " 吃包子~~~" + (i + 1));

            }
            try {
                System.out.println("大龍休息中~~");
                Thread.sleep(20 * 1000);
            } catch (InterruptedException e) {
                //InterruptedException 是捕獲到一個終端異常
                System.out.println(Thread.currentThread().getName() + "被 interrupt了");
            }
        }
    }
}

常用方法第二組

注意:

  • 在某一個執行緒後使用了類名.join方法,那麼會cpu會全部去執行 這個插隊的執行緒,並且當這個執行緒全部執行完後,才會繼續執行原來的執行緒
package com.hspedu.method;

/**
 * @author DL5O
 * @version 1.0
 */
public class ThreadMethod02 {
    public static void main(String[] args) throws InterruptedException {
        int count = 0;
        Test test = new Test();
        test.start();
        while (true){
            System.out.println("hello " + (++count));
            Thread.sleep(1000);
            if(count == 5){
                System.out.println("讓test 執行緒先執行");
//                test.join();//執行緒插隊一定會成功
                Thread.yield();//執行緒禮讓

                //讓test 執行緒先執行,執行後可以看到,當執行join後,主執行緒就不再執行,
                // 當這個test執行緒執行完畢後,主執行緒才繼續執行
            }
            if(count == 20){
                break;
            }
        }


    }
}

class Test extends Thread{
    private int count = 0;
    @Override
    public void run() {
        while(true){
            System.out.println("hi " + (++count));
            try {
                Thread.sleep( 1000);//休眠1秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (count == 20){
                break;
            }
        }

    }
}

課堂練習

package com.hspedu.method;

/**
 * @author DL5O
 * @version 1.0
 */
public class ThreadMethodExercise {
    public static void main(String[] args) throws InterruptedException {
        T01 t01 = new T01();
        for (int i = 1; i <= 10; i++) {
            System.out.println("hi " + i);
            if (i == 5) {
                Thread thread = new Thread(t01);
                thread.start();
                thread.join();
                System.out.println("子執行緒結束...");
            }
            Thread.sleep(1000);
        }
        System.out.println("主執行緒結束...");
    }
}

class T01 implements Runnable{

    @Override
    public void run() {

        for (int i = 1; i <= 10 ; i++) {
            System.out.println("hello " + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

八、使用者執行緒和守護執行緒

  1. 使用者執行緒:也叫工作執行緒,當執行緒的任務執行完或通知方式結束
  2. 守護執行緒:一般是為工作執行緒服務的,當所有的使用者執行緒結束,守護執行緒自動結束
  3. 常見的守護執行緒:垃圾回收機制
package com.hspedu.method;

/**
 * @author DL5O
 * @version 1.0
 * 守護執行緒的設定
 */
public class ThreadMethod03 {
    public static void main(String[] args) throws InterruptedException {
        MyDaemonThread myDaemonThread = new MyDaemonThread();
        Test01 test01 = new Test01();
        myDaemonThread.setDaemon(true);
        //如果我們希望當main執行緒結束後,子執行緒可以自動退出
        //只需將子執行緒設為守護執行緒即可
        myDaemonThread.start();
        test01.start();

        for (int i = 1; i <= 10; i++) {
            System.out.println("我是主執行緒 " + i);
            Thread.sleep(1000);
        }

    }
}

class MyDaemonThread extends Thread {
    @Override
    public void run() {
        for (int i = 1; ; i++ ) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是MyDaemonThread 守護執行緒 " + i);
        }
    }
}

class Test01 extends Thread {
    @Override
    public void run() {
        for (int i = 1; i <= 15 ; i++ ) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是Test01 使用者執行緒 " + i);
        }
    }
}

總結:

  • 當所有的使用者執行緒退出後,守護執行緒就會自動結束
  • 設定守護執行緒的方式:物件名.setDaemon(true)
  • 如果不進行設定,執行緒預設為使用者執行緒

九、 執行緒的生命週期

執行緒狀態轉換圖:


九、執行緒同步機制

關鍵字:Synchronized

  1. 在多執行緒程式設計,一些敏感資料不允許被多少個執行緒同時訪問,此時就使用同步訪問技術保證資料在任何同一時刻,最多有一個執行緒訪問,以保證資料的完整性
  2. 也可以這裡理解:執行緒同步,即當有一個執行緒在對記憶體進行操作時,其他執行緒都不可以對這個記憶體地址進行操作,直到該執行緒完成操作,其他執行緒才能對該記憶體地址進行操作

同步具體方法-Synchronized

  1. 同步程式碼塊

    synchronized(物件){//獲得物件的鎖,才能操作同步程式碼
        //需要被同步程式碼;
    }
    
  2. synchronized還可以放在方法宣告中,表示整個方法-為同步方法

    public synchronized void m(String name){
        //需要被同步的程式碼
    }
    
  3. 如何理解:

    • 就好像某個人要上廁所,上廁所前需要把門關上(上鎖),完事之後再出來(解鎖),那麼其他小夥伴就可以在使用廁所了
  4. 使用synchronized解決售票

package com.hspedu.syn;

/**
 * @author DL5O
 * @version 1.0
 * 使用多執行緒,模擬三個視窗同時售票
 * 總票數 100 張
 */
public class SellTicket {
    public static void main(String[] args) {
        //第一種方式
        //測試
        /*SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();

        sellTicket01.start();
        sellTicket02.start();
        sellTicket03.start();*/

        /*System.out.println("===使用介面的方式來售票===");
        SellTicket02 sellTicket02 = new SellTicket02();
        Thread thread1 = new Thread(sellTicket02);
        Thread thread2 = new Thread(sellTicket02);
        Thread thread3 = new Thread(sellTicket02);
        thread1.start();//第一個執行緒-視窗
        thread2.start();//第二個執行緒-視窗
        thread3.start();//第三個執行緒-視窗*/

        //測試
        SellTicket03 sellTicket03 = new SellTicket03();
        Thread thread1 = new Thread(sellTicket03);
        Thread thread2 = new Thread(sellTicket03);
        Thread thread3 = new Thread(sellTicket03);
        thread1.start();//第一個執行緒-視窗
        thread2.start();//第二個執行緒-視窗
        thread3.start();//第三個執行緒-視窗
    }
}


//實現介面的方式,使用synchronized 實現執行緒同步
class SellTicket03 implements Runnable {
    private int ticketNum = 100;
    private boolean loop = true;

    public synchronized void sell() {
        //同步方法,在同一個時刻只能有一個執行緒來執行我們的run方法
        if (ticketNum <= 0) {
            loop = false;
            System.out.println("票已售空,售票結束..");
            return;
        }

        //休眠50毫秒
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("視窗 " + Thread.currentThread().getName()
                + " 售出一張票," + "剩餘票數=" + (--ticketNum));
    }


    @Override
    public void run() {

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


//使用第一種繼承thread的方式
class SellTicket01 extends Thread {
    private static int ticketNum = 100;//讓多個執行緒共享 ticketNum

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票結束..");
                break;
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("視窗 " + Thread.currentThread().getName()
                    + " 售出一張票" + "剩餘票數=" + (--ticketNum));
        }
    }
}

//實現介面的方式
class SellTicket02 implements Runnable {
    private int ticketNum = 100;

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票結束..");
                break;
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("視窗 " + Thread.currentThread().getName()
                    + " 售出一張票" + "剩餘票數=" + (--ticketNum));
        }
    }
}

十、互斥鎖

基本介紹:

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

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

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

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

  5. 同步方法(非靜態的)的鎖可以是this,也可以是其他物件(要求是同一個物件)

    • 非靜態的這個鎖是加在當前物件的
  6. 同步方法(靜態的)的鎖為當前類本身。

    • 靜態方法的是載入當前類的

注意事項:

  1. 同步方法如果沒有使用static修飾:預設鎖物件是:this
  2. 如果方法使用了static修飾:預設是鎖物件是:當前類.class
  3. 實現的落地步驟:
    • 需要先分析上鎖的程式碼
    • 選擇同步程式碼塊或同步方法
    • 要求多個執行緒的鎖物件為同一個即可!!!!
      • 即共享的資源上
      • 執行緒同步只會發生在共享同一個資源時
package com.hspedu.syn;

/**
 * @author DL5O
 * @version 1.0
 * 使用多執行緒,模擬三個視窗同時售票
 * 總票數 100 張
 */
public class SellTicket {
    public static void main(String[] args) {
        //第一種方式
        //測試
        /*SellTicket01 sellTicket01 = new SellTicket01();
        SellTicket01 sellTicket02 = new SellTicket01();
        SellTicket01 sellTicket03 = new SellTicket01();

        sellTicket01.start();
        sellTicket02.start();
        sellTicket03.start();*/

        /*System.out.println("===使用介面的方式來售票===");
        SellTicket02 sellTicket02 = new SellTicket02();
        Thread thread1 = new Thread(sellTicket02);
        Thread thread2 = new Thread(sellTicket02);
        Thread thread3 = new Thread(sellTicket02);
        thread1.start();//第一個執行緒-視窗
        thread2.start();//第二個執行緒-視窗
        thread3.start();//第三個執行緒-視窗*/

        //測試
        SellTicket03 sellTicket03 = new SellTicket03();
        Thread thread1 = new Thread(sellTicket03);
        Thread thread2 = new Thread(sellTicket03);
        Thread thread3 = new Thread(sellTicket03);
        thread1.start();//第一個執行緒-視窗
        thread2.start();//第二個執行緒-視窗
        thread3.start();//第三個執行緒-視窗
    }
}


//實現介面的方式,使用synchronized 實現執行緒同步
class SellTicket03 implements Runnable {
    private int ticketNum = 100;
    private boolean loop = true;
    Object obj =new Object();//都是同一個物件

    //同步方法(靜態的) 的鎖為當前類
    //1.他的鎖是載入我們這個類上的 SellTicket03.Class 上
    //2.如果要在靜態方法中實現一個同步程式碼塊.
    //      synchronized (SellTicket03.class)
    public synchronized static void m1(){

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

    //是在方法上加的鎖
    //說明
    //1.public synchronized void sell()就是一個同步方法
    //2.這時鎖是在this物件
    //3.也可以在程式碼塊上寫 synchronized,同步程式碼塊,互斥鎖還是在this物件
    public /*synchronized*/ void sell() {//同步方法,在同一個時刻只能有一個執行緒來執行我們的run方法
        synchronized (/*this*/obj){
            if (ticketNum <= 0) {
                loop = false;
                System.out.println("票已售空,售票結束..");
                return;
            }

            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("視窗 " + Thread.currentThread().getName()
                    + " 售出一張票," + "剩餘票數=" + (--ticketNum));
        }
    }


    @Override
    public void run() {

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


//使用第一種繼承thread的方式
//new SellTicket01().start()
//new SellTicket01().start() this只對當前的物件有效
class SellTicket01 extends Thread {
    private static int ticketNum = 100;//讓多個執行緒共享 ticketNum

    //鎖一般用於共享的只有
    public void m1(){
        synchronized (this){
            System.out.println("hello");
        }
    }

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票結束..");
                break;
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("視窗 " + Thread.currentThread().getName()
                    + " 售出一張票" + "剩餘票數=" + (--ticketNum));
        }
    }
}

//實現介面的方式
class SellTicket02 implements Runnable {
    private int ticketNum = 100;

    @Override
    public void run() {
        while (true) {
            if (ticketNum <= 0) {
                System.out.println("售票結束..");
                break;
            }
            //休眠50毫秒
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("視窗 " + Thread.currentThread().getName()
                    + " 售出一張票" + "剩餘票數=" + (--ticketNum));
        }
    }
}

十一、執行緒的死鎖

基本介紹:

多個執行緒都佔用了對方的鎖資源,但不肯相讓,導致了死鎖,在程式設計時一定要避免死鎖的發生

package com.hspedu.syn;

/**
 * @author DL5O
 * @version 1.0
 * 模擬執行緒死鎖
 */

public class DeadLock {
    public static void main(String[] args) {
        DeadLockDemo A = new DeadLockDemo(true);
        A.setName("A執行緒");
        DeadLockDemo B = new DeadLockDemo(false);
        B.setName("B執行緒");
        A.start();
        B.start();
    }
}

//執行緒
class DeadLockDemo extends Thread{
    static Object o1 = new Object();
    static Object o2 = new Object();
    boolean flag;

    public DeadLockDemo(boolean flag){
        this.flag = flag;
    }

    @Override
    public void run() {
        //1.如果flag為T,執行緒就會先得到/持有 o1的物件鎖,然後會嘗試去得到o2的對先生
        //  當持有到 o2的物件鎖時,才會繼續往下執行
        //2.如果執行緒A 得不到o2的物件鎖,就會Blocked
        //3.如果flag 為 false,執行緒會去得到o2的物件鎖,
        //4.如果執行緒B 拿不到o1的物件鎖,就會block
        if(flag){
            synchronized(o1){//物件互斥鎖
                System.out.println(Thread.currentThread().getName()+"進入1");
                //拿到o1的鎖之後會阻塞在這裡,直到拿到o2的鎖,
                // 此時第二個執行緒thread2進入,拿到o2的鎖,進入堵塞狀態,因為一直拿不到o1的鎖,
                // o1又因為拿不到o2鎖,釋放不了,故成為了死鎖
                synchronized (o2){
                    System.out.println(Thread.currentThread().getName()+"進行2");
                }
            }
        }else{
            synchronized (o2){

                //第二個執行緒 o1的鎖已經被拿了,
                //這時,又想去拿o1的鎖,但是o1在第36行阻塞到了,沒有拿到o2的鎖,沒有釋放,故這裡也會被阻塞
                //那麼這時就會產生一種死鎖的情況,要避免
                System.out.println(Thread.currentThread().getName()+"進入3");
                synchronized (o1){
                    System.out.println(Thread.currentThread().getName()+"進行4");
                }
            }
        }
    }
}


十二、釋放鎖

下面操作會釋放鎖

  1. 當執行緒的同步方法、同步程式碼塊執行結束
  2. 當前執行緒在同步程式碼塊、同步方法中遇到break、return
  3. 當前執行緒在同步程式碼塊、同步方法中出現了未處理的errorException,導致異常結束
  4. 當前執行緒在同步程式碼塊、同步方法中執行了執行緒物件的wait()方法,當前執行緒暫停,也會釋放鎖

下面操作不會釋放鎖

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

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

    提示:應儘量避免使用suspend()resume()來控制執行緒,方法不再推薦使用了


作業

package com.hspedu.homework;

import java.util.Random;
import java.util.Scanner;

/**
 * @author DL5O
 * @version 1.0
 */
public class Homework01 {
    public static void main(String[] args) {
        A a = new A();
        B b = new B(a);
        a.setName("A");
        b.setName("B");
//        a.setDaemon(true);
        a.start();
        b.start();

    }
}

class A extends Thread {
    private int num;
    static boolean loop = true;
    private Random random;

    public A() {
        random = new Random();
    }

    public void showNum() {
        //nextInt(int bound)
        //返回偽隨機的,均勻分佈 int值介於0(含)和指定值(不包括),從該隨機數生成器的序列繪製。
        num = random.nextInt(101);//生成0~100的隨機數
        //num = (int)Math.random()*(100-0+1)+0;
        System.out.println("隨機數:" + num);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }


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

//用來控制A執行緒退出的執行緒B
class B extends Thread {
    private String str;
    private A a;
    Scanner scanner;

    public B(A a) {
        this.a = a;
        scanner = new Scanner(System.in);
    }

    @Override
    public void run() {
        synchronized (B.class) {
            while (true) {
                str = scanner.next();
                if (str.equals("Q")) {
                    a.loop = false;
                    break;
                }
            }
        }
    }
}
package com.hspedu.homework;

/**
 * @author DL5O
 * @version 1.0
 */
public class Homework02 {
    public static void main(String[] args) {
        Withdraw withdraw = new Withdraw();
        Thread A = new Thread(withdraw);
        Thread B = new Thread(withdraw);
        A.setName("A");
        B.setName("B");
        A.start();
        B.start();
    }
}

class Withdraw  implements Runnable {
    private int money = 10000;
    private boolean loop = true;

    public void deal() {

        //1.這裡使用了synchronized 實現了執行緒同步
        //2.當多個執行緒執行到這裡的時候,就回去爭奪 this物件鎖
        //3.那個執行緒執行到 this物件鎖,就執行這個程式碼塊,執行玩後,會釋放這個鎖,準備繼續爭奪
        //4.爭奪不到就阻塞到這裡,就blocked
        //5.this 是一個非公平鎖

        synchronized (/*Withdraw.class*/this){
            if (money <= 0) {
                loop = false;
                System.out.println("餘額不足");
                return;
            }
            money -= 1000;
            System.out.println(Thread.currentThread().getName() + "取走了1000,剩餘" + money);
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

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