JAVA併發編程梳理與學習二(線程之間的共享和協作)

一、線程間的共享
synchronized (底層原理分析jvm時會說到)內置鎖
Java 支持多個線程同時訪問一個對象或者對象的成員變量,關鍵字 synchronized 可以修飾方法或者以同步塊的形式來進行使用,它主要確保多個線程在同一個時刻,只能有一個線程處於方法或者同步塊中,它保證了線程對變量訪問的可見性和排他性,又稱爲內置鎖機制
對象鎖和類鎖
對象鎖是用於對象實例方法,或者一個對象實例上的,類鎖是用於類的靜態 方法。我們知道,類的對象實例可以有很多個,但 是每個類只有一個 class 對象,所以不同對象實例的對象鎖是互不干擾的,但是每個類只有一個類鎖。 但是有一點必須注意的是,其實類鎖只是一個概念上的東西,並不是真實存在的,它只是用來幫助我們理解鎖定實例方法和靜態方法的區別的。類鎖和對象鎖之間也是互不干擾的。
一個問題:執行下面代碼synchronized爲什麼沒用


    private Integer i;

    public TestIntegerThread(Integer i){
        this.i=i;
    }

    @Override
    public void run() {

        synchronized (i){
            i++;
            System.out.println(i);
        }
    }

    public static void main(String[] args){
        TestIntegerThread testIntegerThread=new TestIntegerThread(0);
        for(int i=0;i<5;i++){
            new   Thread(testIntegerThread).start();
        }

    }

這是執行結果,我們期望的是1,2,3,4,5
在這裏插入圖片描述
注意:synchronized鎖的是對象,一定要保證所鎖對象的不變性
二、volatile,最輕量的同步機制(底層原理後面分析)
volatile 保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某 個變量的值,這新值對其他線程來說是立即可見的
volatile 最適用的場景:一個線程寫,多個線程讀。
三、ThreadLocal
1.ThreadLocal定義:從名字看ThreadLocal叫線程變量,填充的變量屬於當前線程,每個線程都提供了變量的副本,使得每個線程在某一時間訪問到的並非同一個對象,這樣就隔離了多個線程對數據的數據共享。
2.使用場景:
1》在進行對象跨層傳遞的時候,使用ThreadLocal可以避免多次傳遞,打破層次間的約束。
2》線程間數據隔離
3》進行事務操作,用於存儲線程事務信息。
4》數據庫連接,Session會話管理。
spring事務管理應用了ThreadLocal
我們代碼一般都採用三層結構,我們會在service層調用dao方法,在dao對象的每個方法當中去打開事務和關閉事務。如何讓多個 dao 使用同一個數據源連接呢?我們就必須爲每個 dao傳遞同一個數據庫連接,要麼就是在 dao實例化的時候作爲構造方法的參數傳遞,要麼在每個dao的實例方法中作爲方法的參數傳遞。這兩種方式無疑對我們的 Spring 框架或者開發人員來說都不合適。爲了讓這個數據 庫連接可以跨階段傳遞,又不顯示的進行參數傳遞,就可以用ThreadLocal。
Web 容器中,每個完整的請求週期會由一個線程來處理。因此,如果我們能將一些參數綁定到線程的話,就可以實現在軟件架構中跨層次的參數共享(是隱式的共享)。
ThreadLocal 的使用
ThreadLocal 有 4 個方法:
• void set(Object value) 設置當前線程的線程局部變量的值。
• public Object get() 該方法返回當前線程所對應的線程局部變量。
• public void remove() 將當前線程局部變量的值刪除,目的是爲了減少內存的佔用,該方法是 JDK 5.0 新增的方法。需要指出的是,當線程結束後,對應該線程的局部變量將自動 被垃圾回收,所以顯式調用該方法清除線程的局部變量並不是必須的操作,但它可以加快內存回收的速度。
• protected Object initialValue() 返回該線程局部變量的初始值,該方法是一個 protected 的方法,顯然是爲 了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第 1 次調用 get() 或 set(Object)時才執行,並且僅執行 1 次。ThreadLocal 中的缺省實現直接返回一 個 null。
public final static ThreadLocal threadLocal= new ThreadLocal();
threadLocal代表一個能夠存放String類型的ThreadLocal對象。
ThreadLocal分析
1.看set方法源碼
在這裏插入圖片描述
原碼可以看出,get方法先獲取當前線程,然後獲取getMap方法獲取ThreadLocalMap,如果map不爲null,則將當前線程對象t作爲key,要存儲對象爲value存到map裏面去。如果該Map不存在,則初始化一個。
ThreadLocalMap解讀
在這裏插入圖片描述
ThreadLocalMap其實就是ThreadLocal的一個靜態內部類,裏面定義了一個Entry來保存數據,繼承的弱引用。在Entry內部使用ThreadLocal作爲key,我們設置的value作爲value。 Thread 類中有一個這樣類型成員,所以 getMap 是直接返回 Thread 的成員。並且Entry是一個數組,可以存儲多個。
getMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
調用當期線程t,返回當前線程t中的成員變量threadLocals,其實就是ThreadLocalMap。
2.看get源碼
在這裏插入圖片描述
我們也是先獲取當前線程,然後通過getMap獲取ThreadLocalMap,如果不爲null,那就使用當前線程作爲ThreadLocalMap的Entry的鍵值,去取對應的Entry,如果沒有那就設置一個初始值。ThreadLocal本身並不存儲值,它只是作爲一個key來讓線程從ThreadLocalMap獲取value。
3.設置初始值
protected Object initialValue()
返回該線程局部變量的初始值,該方法是一個 protected 的方法,顯然是爲 了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第 1 次調用 get() 或 set(Object)時才執行,並且僅執行 1 次。ThreadLocal 中的缺省實現直接返回一 個null。

private static ThreadLocal<Integer> intLocal
            = new ThreadLocal<Integer>(){
        @Override
        protected Integer initialValue() {
            return 1;
        }
    };

使用ThreadLocal引發的內存泄漏問題
上面我們說了ThreadLocal是一個弱引用,我們先說說強引用、軟引用、弱引用、虛引用
**強引用:**是指在程序代碼之中普遍存在的,類似“Object obj=new Object()” 這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象實例。
**軟引用:**在 JDK 1.2 之後,提供了 SoftReference 類來實現軟引用。用來描述一些還有用但並非必需的對象。 在系統將要發生內存溢出異常之前,將會把這些對象實例列進回收範圍之中進行第二次回收。
**弱引用:**在 JDK 1.2 之 後,提供了 WeakReference 類來實現弱引用。用來描述非必需對象的,被弱引用關聯的對象實例只能生存到下一次垃圾收集發生之前。當垃圾收集器工作時, 無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象實例。
**虛引用:**在 JDK 1.2 之後,提供了 PhantomReference 類來實現虛引用。也稱爲幽靈引用或者幻影引用,它是最弱的一種引用關係。一個對象實例是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來取得一個對象實例。爲一個對象設置虛引用關聯的唯一目的就是能在這個對象實例被收集器回收時收到一個系統通知。
發生內存泄漏原因:
在這裏插入圖片描述
threadlocal 作爲弱引用將會被 gc 回收。ThreadLocalMap 中就會出現 key 爲 null 的 Entry,就沒有辦法訪問這些 key 爲 null 的 Entry 的 value,但是下面的一條線當前線程並未結束,這些 key 爲 null 的 Entry 的 value 就會一直存在一條強引用鏈:CurrentThread Ref -> CurrentThread -> ThreaLocalMap -> Entry -> value,而這塊 value 永 遠不會被訪問到了,所以存在着內存泄露。
**解決辦法:**不在需要使用 ThreadLocal 變量後,都調remove()方法,清除數據,而remove也是通過expungeStaleEntry來把entry對象置爲null的。 但是我們仔細觀察ThreadLocal的get()、set()方法,裏面也有調用了 expungeStaleEntry 方法用來清除 Entry 中 Key 爲 null 的 Value,但是這是不及時的,也不是每次都會執行的,所以一些情況下還是會發生內存泄露。
四、等待/通知機制
可以用wait/notify()、notifyAll()實現
**wait:**調用該方法的線程會進入等待狀態,只有等待另外線程的通知或被中斷纔會返回.需要注意,調用 wait()方法後,會釋放對象的鎖
notify(): 通知一個在對象上等待的線程,使其從 wait 方法返回,而返回的前提是該線程獲取到了對象的鎖,沒有獲得鎖的線程重新進入等待狀態。 notifyAll(): 通知所有等待在該對象上的線程

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章