多線程——系列六

volatile 關鍵字的做用(變量可見性、禁止重排序)
Java 語言提供了一種稍弱的同步機制,即 volatile 變量,用來確保將變量的更新操做通知到其餘
線程。volatile 變量具有兩種特性,volatile 變量不會被緩存在寄存器或者對其餘處理器不可見的
地方,所以在讀取 volatile 類型的變量時總會返回最新寫入的值。
 
變量可見性
其一是保證該變量對全部線程可見,這裏的可見性指的是當一個線程修改了變量的值,那麼新的
值對於其餘線程是能夠當即獲取的
禁止重排序
volatile 禁止了指令重排。
sychronized 更輕量級的同步鎖
在訪問 volatile 變量時不會執行加鎖操做,所以也就不會使執行線程阻塞,所以 volatile 變量是一
種比 sychronized 關鍵字更輕量級的同步機制。volatile 適合這種場景:一個變量被多個線程共
享,線程直接給這個變量賦值。
當對非 volatile 變量進行讀寫的時候,每一個線程先從內存拷貝變量到 CPU 緩存中。若是計算機有
多個 CPU,每一個線程可能在不一樣的 CPU 上被處理,這意味着每一個線程能夠拷貝到不一樣的 CPU
cache 中。而聲明變量是 volatile 的,JVM 保證了每次讀變量都從內存中讀,跳過 CPU cache
這一步
適用場景
值得說明的是對 volatile 變量的單次讀/寫操做能夠保證原子性的,如 long 和 double 類型變量,
可是並不能保證 i++這種操做的原子性,由於本質上 i++是讀、寫兩次操做。在某些場景下能夠
代替 Synchronized。可是,volatile 的不能徹底取代 Synchronized 的位置,只有在一些特殊的場
景下,才能適用 volatile。總的來講,必須同時知足下面兩個條件才能保證在併發環境的線程安
全:
(1)對變量的寫操做不依賴於當前值(好比 i++),或者說是單純的變量賦值(boolean
flag = true)。
(2)該變量沒有包含在具備其餘變量的不變式中,也就是說,不一樣的 volatile 變量之間,不
能互相依賴。 只有在狀態真正獨立於程序內其餘內容時才能使用 volatile
如何在兩個線程之間共享數據
Java 裏面進行多線程通訊的主要方式就是共享內存的方式,共享內存主要的關注點有兩個:可見
性和有序性原子性。Java 內存模型(JMM)解決了可見性和有序性的問題,而鎖解決了原子性的
問題,理想狀況下咱們但願作到「同步」和「互斥」。有如下常規實現方法:
將數據抽象成一個類,並將數據的操做做爲這個類的方法
1. 將數據抽象成一個類,並將對這個數據的操做做爲這個類的方法,這麼設計能夠和容易作到
同步,只要在方法上加」synchronized「
public class MyData{
    private int j=0;
    public synchronized void  add(){
        j++;
        System.out.println("線程"+Thread.currentThread().getName()+"j:"+j);

    }
    public synchronized  void dec(){
        j--;
        System.out.println("線程"+Thread.currentThread().getName()+"j:"+j);

    }
    public int getData(){
        return j;
    }
}
public class AddRunnable implements Runnable {
    MyData  data;
    public AddRunnable(MyData data){
        this.data=data;
    }
    public void run(){
        data.add();
    }
}
public class DecRunnable implements Runnable {
    MyData data;
    public  DecRunnable(MyData data){
        this.data=data;
    }
    public void run(){
        data.dec();
    }

    public static void main(String[] args) {
        MyData data=new MyData();
        Runnable add=new AddRunnable(data);
        Runnable dec=new DecRunnable(data);
        for (int i = 0; i < 2; i++) {
            new Thread(add).start();
            new Thread(dec).start();
        }
    }
}
Runnable 對象做爲一個類的內部類
2. 將 Runnable 對象做爲一個類的內部類,共享數據做爲這個類的成員變量,每一個線程對共享數
據的操做方法也封裝在外部類,以便實現對數據的各個操做的同步和互斥,做爲內部類的各
個 Runnable 對象調用外部類的這些方法。
public class TestThread {
    public static void main(String[] args) {
        final  MyData data=new MyData();
        for (int i = 0; i < 2; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    data.add();
                }
            }).start();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    data.dec();
                }
            }).start();
        }
    }
}
synchronized ReentrantLock 的區別
. 二者的共同點:
1. 都是用來協調多線程對共享對象、變量的訪問
2. 都是可重入鎖,同一線程能夠屢次得到同一個鎖
3. 都保證了可見性和互斥性
二者的不一樣點:
1. ReentrantLock 顯示的得到、釋放鎖,synchronized 隱式得到釋放鎖
2. ReentrantLock 可響應中斷、可輪迴,synchronized 是不能夠響應中斷的,爲處理鎖的
不可用性提供了更高的靈活性
3. ReentrantLock 是 API 級別的,synchronized 是 JVM 級別的
4. ReentrantLock 能夠實現公平鎖
5. ReentrantLock 經過 Condition 能夠綁定多個條件
6. 底層實現不同, synchronized 是同步阻塞,使用的是悲觀併發策略,lock 是同步非阻
塞,採用的是樂觀併發策略
7. Lock 是一個接口,而 synchronized 是 Java 中的關鍵字,synchronized 是內置的語言
實現。
8. synchronized 在發生異常時,會自動釋放線程佔有的鎖 ,所以不會致使死鎖現象發生;
而 Lock 在發生異常時,若是沒有主動經過 unLock()去釋放鎖,則極可能形成死鎖現象,
所以使用 Lock 時須要在 finally 塊中釋放鎖。
9. Lock 可讓等待鎖的線程響應中斷 ,而 synchronized 卻不行,使用 synchronized 時,
等待的線程會一直等待下去,不可以響應中斷。
10. 經過 Lock 能夠知道有沒有成功獲取鎖,而 synchronized 卻沒法辦到。
11. Lock 能夠提升多個線程進行讀操做的效率,既就是實現讀寫鎖等。
 
ConcurrentHashMap 併發
減少鎖粒度
減少鎖粒度是指縮小鎖定對象的範圍,從而減少鎖衝突的可能性,從而提升系統的併發能力。減
小鎖粒度是一種削弱多線程鎖競爭的有效手段,這種技術典型的應用是 ConcurrentHashMap(高
性能的 HashMap)類的實現。對於 HashMap 而言,最重要的兩個方法是 get 與 set 方法,若是我
們對整個 HashMap 加鎖,能夠獲得線程安全的對象,可是加鎖粒度太大。 Segment 的大小也被
稱爲 ConcurrentHashMap 的併發度
 
ConcurrentHashMap 分段鎖
ConcurrentHashMap,它內部細分了若干個小的 HashMap,稱之爲段(Segment)。 默認狀況下
一個 ConcurrentHashMap 被進一步細分爲 16 個段 ,既就是鎖的併發度。
若是須要在 ConcurrentHashMap 中添加一個新的表項,並非將整個 HashMap 加鎖,而是首
先根據 hashcode 獲得該表項應該存放在哪一個段中,而後對該段加鎖,並完成 put 操做。在多線程
環境中,若是多個線程同時進行 put操做,只要被加入的表項不存放在同一個段中,則線程間能夠
作到真正的並行。
 
ConcurrentHashMap 是由 Segment 數組結構和 HashEntry 數組結構組成
ConcurrentHashMap 是由 Segment 數組結構和 HashEntry 數組結構組成。Segment 是一種可
重入鎖 ReentrantLock,在 ConcurrentHashMap 裏扮演鎖的角色,HashEntry 則用於存儲鍵值
對數據。一個 ConcurrentHashMap 裏包含一個 Segment 數組,Segment 的結構和 HashMap
相似,是一種數組和鏈表結構, 一個 Segment 裏包含一個 HashEntry 數組,每一個 HashEntry 是
一個鏈表結構的元素, 每一個 Segment 守護一個 HashEntry 數組裏的元素 , 當對 HashEntry 數組的
數據進行修改時,必須首先得到它對應的 Segment 鎖
相關文章
相關標籤/搜索