首頁 > 軟體

Java執行緒安全狀態專題解析

2022-03-02 16:01:03

一、觀察執行緒的所有狀態

執行緒的狀態是一個列舉型別 Thread.State

 public static void main(String[] args) {
        for (Thread.State state : Thread.State.values()){
            System.out.println(state);
        }
    }

NEW: 安排了工作, 還未開始行動

RUNNABLE: 可工作的. 又可以分成正在工作中和即將開始工作.就緒狀態

BLOCKED: 這幾個都表示排隊等著其他事情

WAITING: 這幾個都表示排隊等著其他事情

TIMED_WAITING: 這幾個都表示排隊等著其他事情

TERMINATED: 工作完成了.

二、執行緒狀態和狀態轉移的意義

NEW:Thread物件有了,但是PCB還沒有

RUNNABLE:執行緒正在CPU上執行或者即將到CPU上執行(PCB在就緒佇列中,隨時可能被排程到)

WAITING:wait方法導致

TIMED_WAITING:sleep方法導致

BLOCKED:等待鎖導致

TERMINATED:物件還在,但PCB已經沒了

public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100_00; i++){
 
                }
            }
        };
        System.out.println("執行緒啟動前:" + t.getState());
 
        t.start();
        while (t.isAlive()){
            System.out.println("執行緒執行中:" + t.getState());
        }
        System.out.println("執行緒結束後:" + t.getState());
    }

三、多執行緒帶來的風險

執行緒不安全的原因

①執行緒是搶佔式執行的

執行緒不安全的萬惡之源,執行緒之間的排程完全由核心負責,使用者程式碼中感知不到,也無法控制。執行緒之間誰先執行,誰後執行,誰執行到哪裡從CPU上下來,這樣的過程使用者無法控制也無法感知到的。

②自增操作不是原子的

每次++都能拆成三個步驟

        把記憶體中的資料讀取到CPU中

        把CPU中的資料+1

        把計算的資料寫回記憶體中

如果兩個執行緒序列執行,此時計算結果為2。

如果兩個執行緒並行執行,執行緒1進行++操作到一半的時候,執行緒也進行了++操作,此時自增兩次,但結果為1。

必須保證執行緒1save結束了,執行緒2再load,此時計算結果才正確

③多個執行緒嘗試修改同一個變數

如果是一個執行緒修改一個變數,執行緒安全

如果多個執行緒讀取同一個變數,執行緒安全

如果多個執行緒修改不同的變數。執行緒安全

④記憶體可見性導致執行緒安全問題

⑤指令重排序

Java的編譯器在編譯程式碼時,會對指令進行優化,調整指令的先後順序,保證原有的邏輯不變的情況下,提高程式的執行效率

四,解決執行緒安全問題

鎖-synchronized

未加鎖

static class Counter{
        public int count = 0;
 
         public void increase(){
            count++;
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++){
                    counter.increase();
                }
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++){
                    counter.increase();
                }
            }
        };
        t1.start();
        t2.start();
        t1.join();
        t2.join();
 
        System.out.println(counter.count);
    }

 已加鎖

static class Counter{
        public int count = 0;
 
        synchronized public void increase(){
            count++;
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        Thread t1 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++){
                    counter.increase();
                }
            }
        };
        Thread t2 = new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++){
                    counter.increase();
                }
            }
        };
        t1.start();
        t2.start();
        t1.join();
        t2.join();
 
        System.out.println(counter.count);
    }

此處的synchronized就是針對counter這個物件來加鎖,進入increase方法內部,就把加鎖狀態設為ture,increase方法退出之後,就把加鎖狀態設為false,如果某個執行緒已經把加鎖狀態設為ture,此處的其他的執行緒嘗試去加鎖,就會阻塞

synchronized的特性——重新整理記憶體

synchronized 的工作過程:

        1. 獲得互斥鎖

        2. 從主記憶體拷貝變數的最新副本到工作的記憶體

        3. 執行程式碼

        4. 將更改後的共用變數的值重新整理到主記憶體

        5. 釋放互斥鎖

synchronized的特性——互斥

 public static void main(String[] args) {
        Object locker = new Object();
 
        Thread t1 = new Thread(){
            @Override
            public void run() {
                Scanner scanner = new Scanner(System.in);
                synchronized (locker) {
                    System.out.println("輸入一個整數");
                    int num = scanner.nextInt();
                    System.out.println("num= " + num);
                }
            }
        };
        t1.start();
 
        Thread t2 = new Thread(){
            @Override
            public void run() {
                while (true){
                    synchronized (locker){
                        System.out.println("執行緒2獲取到鎖");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        };
        t2.start();
    }

一旦執行緒一獲取到鎖,並且沒有釋放的話,執行緒2就會一直在鎖這裡阻塞等待

 public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();
 
        Thread t1 = new Thread(){
            @Override
            public void run() {
                Scanner scanner = new Scanner(System.in);
                synchronized (locker1) {
                    System.out.println("輸入一個整數");
                    int num = scanner.nextInt();
                    System.out.println("num= " + num);
                }
            }
        };
        t1.start();
 
        Thread t2 = new Thread(){
            @Override
            public void run() {
                while (true){
                    synchronized (locker2){
                        System.out.println("執行緒2獲取到鎖");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        };
        t2.start();
    }

 不是同一把鎖,就不回出現競爭,就沒有互斥了。

public static void main(String[] args) {
        Object locker1 = new Object();
        Object locker2 = new Object();
 
        Thread t1 = new Thread(){
            @Override
            public void run() {
                Scanner scanner = new Scanner(System.in);
                synchronized (locker1.getClass()) {
                    System.out.println("輸入一個整數");
                    int num = scanner.nextInt();
                    System.out.println("num= " + num);
                }
            }
        };
        t1.start();
 
        Thread t2 = new Thread(){
            @Override
            public void run() {
                while (true){
                    synchronized (locker2.getClass()){
                        System.out.println("執行緒2獲取到鎖");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        };
        t2.start();
    }

這個程式碼中,兩個執行緒都在針對locker1和locker2的類物件進行競爭,此處的locker1和locker2的型別都是Object,對應的物件都是相同的物件。 

到此這篇關於Java執行緒安全狀態專題解析的文章就介紹到這了,更多相關Java 執行緒安全內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com