線程安全性問題

線程性能問題

CPU時間片java

上下文切換,切換的時候須要保持任務的狀態, 以便後續接着任務的狀態執行。比較消耗CPU 資源。安全

活躍性問題

  • 死鎖: 雙方都有對方須要的資源,而且都不釋放給對方使用
  • 活鎖:一直互相釋放資源給對方
  • 飢餓: 線程優先級比較低的線程可能一直得不到資源而沒法運行

死鎖問題多線程

能夠經過jconsole 工具來進行檢測:ide

飢餓和活鎖問題很差檢測工具

飢餓與公平

  • 高優先級吞噬全部低優先級的CPU 時間片

如下代碼低優先級的線程仍是有可能大多數狀況下獲取CPU時間片的,只是機率上會變小。性能

public class Target implements Runnable {

    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + "...");
        }
    }
}

public class Demo8 {

    public static void main(String[] args) {
        Thread t = new Thread(new Target());
        Thread t1 = new Thread(new Target());
        Thread t2 = new Thread(new Target());
        Thread t3 = new Thread(new Target());

        t.setPriority(Thread.MAX_PRIORITY);
        //不一樣平臺優先級值不同,建議使用常量
        t2.setPriority(Thread.MIN_PRIORITY);

        t.start();
        t2.start();
    }
}
  • 線程被永久堵塞在一個等待進入同步塊的狀態
  • 等待的線程永遠不被喚醒

如何儘可能避免飢餓問題:線程

  1. 設置合理的優先級
  2. 使用鎖來代替synchronized

線程安全性問題

寫一個數值生成器:3d

單線程環境下:code

public class Sequence {

    private int value;

    public int getNext() {
        return value++;
    }


    public static void main(String[] args) {
        Sequence sequence = new Sequence();
        while (true) {
            System.out.println(sequence.getNext());
        }
    }
}

多線程環境:對象

package thread;

public class Sequence {

    private int value;

    public int getNext() {
        return value++;
    }


    public static void main(String[] args) {

        Sequence sequence = new Sequence();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + " " + sequence.getNext());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + " " + sequence.getNext());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + " " + sequence.getNext());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

會有數據重複的問題,這就是線程安全性問題

線程是多個執行順序流,value++ 至關於 value = value + 1, 是兩步操做

從java字節碼角度來看線程安全性問題:

可使用 javap -verbose Sequence.class 來看字節碼文件

0: aload_0
         1: dup
         2: getfield      #2                  // Field value:I
         5: dup_x1
         6: iconst_1
         7: iadd
         8: putfield      #2                  // Field value:I
        11: ireturn

類的實例化對象它是放在堆內存中,堆是線程所共享的區域

程序計數器是線程所獨享的區域

Value 是線程共享的區域

  • 第一個線程執行完,值變爲1 ,只是在操做數棧中變爲1,尚未設置value, 所以value 仍是0
  • 第二個線程獲取到時間片,首先是獲取值 getfield, 獲取的value爲0,由於第一個線程尚未寫進去
  • 第一個線程獲取到時間片,開始把1 往value 放,value 變爲1
  • 第二個線程獲取到時間片,也開始把加完後的1 往value 放,value 仍是1
  • 原本最終應該是2,結果是1,這就是線程安全性問題

如何解決上面代碼的線程安全性問題:

package thread;
public class Sequence {
    private int value;

    public synchronized int getNext() {
        return value++;
    }
    public static void main(String[] args) {

        Sequence sequence = new Sequence();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + " " + sequence.getNext());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + " " + sequence.getNext());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + " " + sequence.getNext());
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

在 getNext() 方法前加了synchronized關鍵字,讓方法變成同步方法就不會再有重複的數據

Synchronized 至關於一個門加了一把鎖,當一個線程執行的時候,就獲取了這把鎖,而後把門鎖上,其餘線程來的時候就要在外面等待,等執行完畢後把鎖釋放,其餘線程才能進來。就會致使同一時刻只會有一個線程執行該段代碼。

什麼狀況會出現線程安全性問題:

  1. 多線程環境
  2. 多個線程共享一個資源
  3. 對資源進行非原子性操做

總結

本文主要介紹了線程的性能問題,死鎖問題以及如何使用jconsole 查看線程是否發生死鎖,線程的飢餓與公平,線程安全性問題:從字節碼角度來分析線程安全性問題、如何解決線程安全的問題以及在什麼狀況下會出現線程安全性問題。

歡迎掃碼關注,第一時間收到最新文章推送

個人博客即將同步至騰訊雲+社區,邀請你們一同入駐:https://cloud.tencent.com/developer/support-plan?invite_code=14jki2ulsej09

相關文章
相關標籤/搜索