【轉】JAVA 並發性和多線程 -- 讀感 (二 線程間通訊,共享內存的機制)
阿新 • • 發佈:2018-03-01
instance bar log 通信 java t 是的 復制代碼 ott bus
2.對象成員
對象成員是存儲在堆上。如果兩個線程同時更新同一個對象的同一成員,這個代碼就是線程不安全的。
原文地址:https://www.cnblogs.com/edenpans/p/6020113.html
參考文章:http://ifeve.com/java-concurrency-thread-directory/
其中的競態,線程安全,內存模型,線程間的通信,java ThreadLocal類小節部分內容。
- 1.目錄略覽
- 2.競態條件與臨界區
- 3.線程安全與共享資源
public void someMethod(){ long threadSafeInt = 0; threadSafeInt++; }
public void someMethod(){ LocalObject localObject = new LocalObject(); localObject.callMethod(); method2(localObject); } public void method2(LocalObject localObject){ localObject.setValue("value"); }
public class NotThreadSage{ StringBuilder builder = New StringBuilder(); public add(String text) { this.builder.append(text); } }
線程控制逃逸判斷
一個資源的創建,使用銷毀都在同一個線程內完成,且永遠不會脫離該線程的控制。
即使對象本身線程安全,但是該對象中包含的其他的資源,也許整體的應用不是線程安全的。 3.線程安全及不可變性 immutable 和 read only 的差別:當一個變量是只讀的時候,變量的值不可改變,但是可以在其他變量發生改變的時候發生改變。而不變 是不會改變的。- 4.java 內存模型
每個java虛擬機的線程都擁有自己的線程棧,包括了這個線程調用的方法當前執行點的相關信息。一個線程只能訪問自己的線程棧。本地變量只對當前線程可見。
對象是放在堆上。 每個線程都有自己的線程棧,如果是基本類型的變量,直接存放在線程棧中,如果是對象的引用,那麽引用地址會放在線程棧中,而對象會在堆中,這樣有可能存在兩個線程同時引用相同的對象。
public class MyRunnable implements Runnable() { public void run() { methodOne(); } public void methodOne() { int localVariable1 = 45; MySharedObject localVariable2 = MySharedObject.sharedInstance; //... do more with local variables. methodTwo(); } public void methodTwo() { Integer localVariable1 = new Integer(99); //... do more with local variable. } } public class MySharedObject { //static variable pointing to instance of MySharedObject public static final MySharedObject sharedInstance = new MySharedObject(); //member variables pointing to two objects on the heap public Integer object2 = new Integer(22); public Integer object4 = new Integer(44); public long member1 = 12345; public long member1 = 67890; }
兩個線程啟動後,Object3就是
MySharedObject,而Object2,Object4 是
MySharedObject中的 object2 和 Object4.
現代硬件內存架構
Java內存模型和硬件內存架構之間的橋接
硬件內存架構中沒有區分線程棧和堆。對於硬件所有線程棧和堆都是分布在主存中。部分線程棧和堆可能出現在CPU緩存和CPU內部的寄存器中。 當對象和變量被存放在計算機不同的內存區域中時,會有一些問題: 1.線程對共享變量修改的可見性。— 兩個線程分布運行在不同的CPU上時,線程的部分變量沒有刷新回主存,此時可能會導致不同步。可以使用 volatile 來避免。 2.當讀,寫和檢查共享變量時出現race conditions。多個線程同時修改共享內存的值,如下圖:
可以使用java同步塊,這樣同一時刻只能有一個線程可以進入代碼的臨界區。同步塊還可以保證代碼塊中所有被訪問的變量從主存中讀入,當線程退出同步塊時,所有被更新的變量也會被刷新回主存中,無論該變量是否被聲明為volatile. 5.java 同步塊 java同步塊 (synchronized block) 用來標記方法或者代碼塊是同步的。用來避免競爭。 java同步關鍵字:synchronized 所有其他等待進入該同步塊的線程將被阻塞,直到執行該同步塊的線程退出。 四種不同的同步塊: 實例方法;靜態方法;實例方法中的同步塊;靜態方法中的同步塊。——都是方法上的同步塊。 實例方法同步:
public synchronized void add(int value){ this.count += value; }每個實例其方法同步都是同步在不同的對象上。這樣每個實例方法同步都同步在不同的對象上,即該方法所屬的實例,只有一個線程可以在實例方法同步塊中運行。一個實例一個線程。 靜態方法同步:
public static synchronized void add(int value){ count += value; }靜態方法同步是指同步在該方法上所在的類對象上的。java虛擬機中一個類只能對應一個類對象,所以同時只允許一個線程執行同一個類中的靜態同步方法。不管類中的哪個靜態同步方法被調用,一個類只能由一個線程同時執行。 實例方法中同步塊:
public void add(int value){ synchronized(this){ this.count += value; } }
示例中使用的this 是代表的調用add方法的實例本身。在同步構造器中用括號括起來的對象叫做監視器對象。
靜態方法中同步塊:public class MyClass { public static synchronized void log1(String msg1, String msg2){ log.writeln(msg1); log.writeln(msg2); } public static void log2(String msg1, String msg2){ synchronized(MyClass.class){ log.writeln(msg1); log.writeln(msg2); } } }兩個方法不允許同時被線程訪問。 如果第二個同步塊不是同步在MyClass.class這個同步器上,這兩個方法可以同時被線程訪問。 java同步示例:
public class Counter{ long count = 0; public synchronized void add(long value){ this.count += value; } } public class CounterThread extends Thread{ protected Counter counter = null; public CounterThread(Counter counter){ this.counter = counter; } public void run() { for(int i=0; i<10; i++){ counter.add(i); } } } public class Example { public static void main(String[] args){ Counter counter = new Counter(); Thread threadA = new CounterThread(counter); Thread threadB = new CounterThread(counter); threadA.start(); threadB.start(); } }
由於兩個線程都是共用一個counter實例,所以add()被調用的時候是同步的,只有一個線程可以調用,另外一個需要等待。
public class Example { public static void main(String[] args){ Counter counterA = new Counter(); Counter counterB = new Counter(); Thread threadA = new CounterThread(counterA); Thread threadB = new CounterThread(counterB); threadA.start(); threadB.start(); } }這個時候兩個線程就可以同時調用add()方法,因為它們分別在不同的實例中。
- 6.線程通信
public class MySignal{ protected boolean hasDataToProcess = false; public synchronized boolean hasDataToProcess(){ return this.hasDataToProcess; } public synchronized void setHasDataToProcess(boolean hasData){ this.hasDataToProcess = hasData; } }兩個線程獲得指向一個MySingal共享實例的引用,以便通信。同時獲取變量的方法設置為同步方法,防止線程不一致。 2.忙等待(Busy Wait)
protected MySignal sharedSignal = ... ... while(!sharedSignal.hasDataToProcess()){ //do nothing... busy waiting }線程B一直在等待數據。但是感覺這裏和前面獲取共享變量是一個原理。 3.wait(),notify()和 notifyAll() wait()調用後就處於非運行狀態,直到另外一個線程調用了同一個對象的notify()方法。同時線程必須獲取這個對象的鎖才能調用。
public class MonitorObject{ } public class MyWaitNotify{ MonitorObject myMonitorObject = new MonitorObject(); public void doWait(){ synchronized(myMonitorObject){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } } public void doNotify(){ synchronized(myMonitorObject){ myMonitorObject.notify(); } } }調用這個對象的notify() 的時候,有一個wait的線程會被隨機喚醒,同時也有一個notifyAll()方法來喚醒所有線程。 一旦線程調用了wait()方法,就釋放了所持有的監視器對象上的鎖,就允許了其他線程也可以調用wait()或者notify().同時一個線程被喚醒不是立刻就退出wait()的方法,直到調用notify()的線程退出了自己的同步塊。 4.丟失信號 由於notify()和notifyAll()不會保存調用它們的方法,他們發送的信號如果在wait()之前就有可能丟失,這個時候必須把他們保存在信號類裏。
public class MyWaitNotify2{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignalled = false; public void doWait(){ synchronized(myMonitorObject){ if(!wasSignalled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } }應該就是借助一個變量來記錄是否調用過Notify()。 5.假喚醒 有時由於莫名其妙的原因,線程可能在沒有掉用過notify()和 notifyAll()的情況下醒來。防止假喚醒,保存信號的成員變量會檢查是否是自己的信號,如果不是的話,就繼續wait()。
public class MyWaitNotify3{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignalled = false; public void doWait(){ synchronized(myMonitorObject){ while(!wasSignalled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } }6.多個線程等待相同信號 while 循環也可以解決當多線程在等待時,只需要喚醒一個線程,並且是使用nitifyAll()來喚醒的情況。 7.不要在字符串常量或全局對象中調用wait() 就是導致假喚醒的原因之一,並且可能會導致信號沒有接收到。 管程 (Monitor)是對多個工作線程實現互斥訪問共享資源的對象和模塊。管程實現了在一個時間點,最多只有一個線程在執行他的某個子程序。
- 6 Java ThreadLocal
【轉】JAVA 並發性和多線程 -- 讀感 (二 線程間通訊,共享內存的機制)