如何結局線程安全問題

什麼是線程安全問題?

當多個線程共享一個全局變量,對其作寫操做時,可能會受到其餘線程的干擾,從而引起線程安全問題java

內置鎖(synchronized)

內置鎖也叫互斥鎖,能夠保證線程的原子性,當線程進入方法時,會自動得到一個鎖,一旦鎖被得到,其餘線程必須等待得到鎖的線程執行完代碼釋放鎖,會下降程序的執行效率緩存

使用方式:安全

同步方法markdown

public synchronized void sale() {
    if (trainCount > 0) {
        System.out.println(Thread.currentThread().getName() + "票" + (100 - trainCount + 1) + "張票");
        trainCount --;
    }
}
// 非靜態同步方法使用this鎖
// 靜態同步方法使用當前字節碼文件

同步代碼塊多線程

private Object obj = new Object();
....
public void sale() {
    // 參數爲任意全局對象
    synchronized (obj) {
        if (trainCount > 0) {
            System.out.println(Thread.currentThread().getName() + "票" + (100 - trainCount + 1) + "張票");
            trainCount --;
        }
    }
}

注意事項:ide

public static void main(String[] args) {
    ThreadDemo1 threadDemo1 = new ThreadDemo1();
    ThreadDemo1 threadDemo2 = new ThreadDemo1();
    Thread t1 = new Thread(threadDemo1, "窗口1");
    Thread t2 = new Thread(threadDemo2, "窗口2");
    t1.start();
    t2.start();
}
// 這樣的話也會產生線程安全問題
// 那是由於兩個線程分別由不一樣的線程建立的,它們之間的變量不共享,產生了兩把不一樣的鎖
// 解決方法是在全局變量上加上 static 關鍵字,靜態變量存在方法區,這個類中的全部對象都共享同一個變量

重入鎖和不可重入鎖性能

重入鎖:即得到鎖的線程能夠進入它擁有的鎖的同步代碼塊優化

不可重入鎖:即得到鎖的線程,在方法中嘗試再次得到鎖時,獲取不到進入阻塞狀態this

死鎖產生的緣由線程

同步中嵌套同步,同步鎖是一個重入鎖,就頗有可能發生死鎖

ThreadLocal

爲每一個線程提供局部變量,解決線程安全問題

ThreadLocal 底層採用 Map 來實現,將當前線程做爲key,將值存儲到這個 map 中

class Res {
    private Integer count = 0;
    ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<Integer>() {
        @Override
        protected Integer initialValue() {
            return 0;
        }
    };

    public Integer getCount() {
        int count = integerThreadLocal.get() + 1;
        integerThreadLocal.set(count);
        return count;
    }
}

多線性特性

什麼是原子性?

即一個或一組操做,要麼所有執行,執行過程當中不會被其餘線程打斷,要麼所有不執行

什麼是可見性?

多線程操做中一個線程修改了全局共享變量的值,其餘線程能立馬獲得修改後的值

什麼是有序性?

程序執行的順序按照代碼的前後順序執行,通常來講處理器爲了提升程序運行效率,可能會對輸入代碼進行優化,它不保證程序中各個語句的執行前後順序同代碼中的順序一致,可是它會保證程序最終執行結果和代碼順序執行的結果是一致的

Java 內存模型(JMM)

JMM 決定一個線程對一個共享變量作寫操做時,可否對另外一個線程可見

主內存:共享變量

本地內存:共享變量副本

多線程作修改操做時,首先從主內存中拷貝一份副本到本地內存中,當線程修改本地內存的值後,首先在本地內存修改爲功,而後再將修改後的結果刷新到主內存中

Volatile

可見性就是說一旦某個線程修改了被 Volatile 修飾的變量,其餘線程能立馬獲取到修改後的新值,在 Java 中爲了加快程序的運行效率,對一些變量的操做一般是在該線程的寄存器或者 CPU 緩存中進行的,以後纔會同步到主存,而加了 Volatile 關鍵字後會直接讀寫內存

注意:雖然該關鍵字可以保證可見性,但不能保證原子性

特性:

  • 保證可見性
  • 禁止指令重排(CPU採用了容許將多條指令不按程序規定的順序分開發送給各相應電路單元處理),使用Volatile 修飾的變量,賦值後多執行了一個"load addl $0x0, (%esp)"操做,這個操做至關於一個內存屏障(指令重排序時不能把後面的指令重排序到內存屏障以前的位置),只有一個CPU訪問內存時,並不須要內存屏障。

Volatile 與 Synchronized 區別:

Volatile 雖然能保證可見性,但不能保證原子性

Synchronized 防止多個線程執行同一塊代碼,影響執行效率,就性能而言,Volatile 是高於 Synchronized 的。

可是 Volatile 是不能取代 Synchronized 的,由於 Volatile 不能保證原子性。

重排序

數據依賴

若是兩個操做同時操做一份變量,且這兩個操做其中有一個寫操做,此時這兩個操做之間就存在數據依賴

名稱 代碼示例 說明
寫後讀 a = 1;b = a; 寫一個變量以後,再讀這個位置。
寫後寫 a = 1;a = 2; 寫一個變量以後,再寫這個變量。
讀後寫 a = b;b = 1; 讀一個變量以後,再寫這個變量

上面三種狀況,只要更改操做的執行順序,結果就會發生改變,編譯器和處理器可能會作重排序,在作重排序時會尊徐數據依賴,因此編譯器和處理器不能對數據依賴的操做重排序,這裏的數據依賴性僅針對單個處理器中執行的指令序列和單線程的操做,不一樣處理器之間和不一樣線程之間的數據依賴性不被編譯器和處理器考慮

as-if-serial語義

無論怎麼重排,結果都不能改變

對多線程的影響

在單線程程序中,對存在數據依賴的操做作重排序不會影響結果,由於單線程尊徐 as-if-serial 語義,但 as-if-serial 語義提到"不考慮不一樣處理器之間和不一樣線程之間的數據依賴",因此,指令重排可能會對多線程的結果產生影響

相關文章
相關標籤/搜索