導航:首頁 > 編程語言 > java中的線程同步

java中的線程同步

發布時間:2024-02-08 08:53:22

1. java 理論與實踐: 正確使用 volatile 變數 線程同步

Java語言規范中指出 為了獲得最佳速度 允許線程保存共享成員變數的私有拷貝 而且只當線程進入或者離開同步代碼塊時才與共享成員變數的原始值對比

這樣當多個線程蔽握同時與某個對象交互時 就必須要注意到要讓線程及時的得到共享成員變數的變化

而volatile關鍵字就是提示VM:對於這個成員變數不能保存它的私有拷貝 而應直接與共享成員變數交互

使用建議 在兩個或者更多的線程訪問的成員變數上使用volatile 當要訪問慎並賣的變數已在synchronized代碼塊中 或者為常量時 不必使用

由於使用volatile屏蔽掉了VM中必要的代碼優化 所以在效率上比較低 因此一定在寬逗必要時才使用此關鍵字

Java的serialization提供了一種持久化對象實例的機制 當持久化對象時 可能有一個特殊的對象數據成員 我們不想用serialization機制來保存它 為了在一個特定對象的一個域上關閉serialization 可以在這個域前加上關鍵字transient

transient是Java語言的關鍵字 用來表示一個域不是該對象串列化的一部分 當一個對象被串列化的時候 transient型變數的值不包括在串列化的表示中 然而非transient型的變數是被包括進去的

注意static變數也是可以串列化的

Java 語言中的 volatile 變數可以被看作是一種 程度較輕的 synchronized ;與 synchronized 塊相比 volatile 變數所需的編碼較少 並且運行時開銷也較少 但是它所能實現的功能也僅是 synchronized 的一部分 本文介紹了幾種有效使用 volatile 變數的模式 並強調了幾種不適合使用 volatile 變數的情形

鎖提供了兩種主要特性 互斥(mutual exclusion) 和可見性(visibility) 互斥即一次只允許一個線程持有某個特定的鎖 因此可使用該特性實現對共享數據的協調訪問協議 這樣 一次就只有一個線程能夠使用該共享數據 可見性要更加復雜一些 它必須確保釋放鎖之前對共享數據做出的更改對於隨後獲得該鎖的另一個線程是可見的 如果沒有同步機制提供的這種可見性保證 線程看到的共享變數可能是修改前的值或不一致的值 這將引發許多嚴重問題

Volatile 變數

Volatile 變數具有 synchronized 的可見性特性 但是不具備原子特性 這就是說線程能夠自動發現 volatile 變數的最新值 Volatile 變數可用於提供線程安全 但是只能應用於非常有限的一組用例 多個變數之間或者某個變數的當前值與修改後值之間沒有約束 因此 單獨使用 volatile 還不足以實現計數器 互斥鎖或任何具有與多個變數相關的不變式(Invariants)的類(例如 start <=end )

出於簡易性或可伸縮性的考慮 您可能傾向於使用 volatile 變數而不是鎖 當使用 volatile 變數而非鎖時 某些習慣用法(idiom)更加易於編碼和閱讀 此外 volatile 變數不會像鎖那樣造成線程阻塞 因此也很少造成可伸縮性問題 在某些情況下 如果讀操作遠遠大於寫操作 volatile 變數還可以提供優於鎖的性能優勢

正確使用 volatile 變數的條件

您只能在有限的一些情形下使用 volatile 變數替代鎖 要使 volatile 變數提供理想的線程安全 必須同時滿足下面兩個條件

對變數的寫操作不依賴於當前值

該變數沒有包含在具有其他變數的不變式中

實際上 這些條件表明 可以被寫入 volatile 變數的這些有效值獨立於任何程序的狀態 包括變數的當前狀態

第一個條件的限制使 volatile 變數不能用作線程安全計數器 雖然增量操作(x++)看上去類似一個單獨操作 實際上它是一個由讀取 修改 寫入操作序列組成的組合操作 必須以原子方式執行 而 volatile 不能提供必須的原子特性 實現正確的操作需要使 x 的值在操作期間保持不變 而 volatile 變數無法實現這點 (然而 如果將值調整為只從單個線程寫入 那麼可以忽略第一個條件 )

大多數編程情形都會與這兩個條件的其中之一沖突 使得 volatile 變數不能像 synchronized 那樣普遍適用於實現線程安全 清單 顯示了一個非線程安全的數值范圍類 它包含了一個不變式 下界總是小於或等於上界

清單 非線程安全的數值范圍類

@NotThreadSafe

public class NumberRange {

private int lower upper;

public int getLower() { return lower; }

public int getUpper() { return upper; }

public void setLower(int value) {

if (value > upper)

throw new IllegalArgumentException(…)

lower = value;

}

public void setUpper(int value) {

if (value < lower)

throw new IllegalArgumentException(…)

upper = value;

}

}

這種方式限制了范圍的狀態變數 因此將 lower 和 upper 欄位定義為 volatile 類型不能夠充分實現類的線程安全 從而仍然需要使用同步 否則 如果湊巧兩個線程在同一時間使用不一致的值執行 setLower 和 setUpper 的話 則會使范圍處於不一致的狀態 例如 如果初始狀態是 ( ) 同一時間內 線程 A 調用 setLower( ) 並且線程 B 調用 setUpper( ) 顯然這兩個操作交叉存入的值是不符合條件的 那麼兩個線程都會通過用於保護不變式的檢查 使得最後的范圍值是 ( ) 一個無效值 至於針對范圍的其他操作 我們需要使 setLower() 和 setUpper() 操作原子化 而將欄位定義為 volatile 類型是無法實現這一目的的

性能考慮

使用 volatile 變數的主要原因是其簡易性 在某些情形下 使用 volatile 變數要比使用相應的鎖簡單得多 使用 volatile 變數次要原因是其性能 某些情況下 volatile 變數同步機制的性能要優於鎖

很難做出准確 全面的評價 例如 X 總是比 Y 快 尤其是對 JVM 內在的操作而言 (例如 某些情況下 VM 也許能夠完全刪除鎖機制 這使得我們難以抽象地比較 volatile和 synchronized 的開銷 )就是說 在目前大多數的處理器架構上 volatile 讀操作開銷非常低 幾乎和非 volatile 讀操作一樣 而 volatile 寫操作的開銷要比非 volatile 寫操作多很多 因為要保證可見性需要實現內存界定(Memory Fence) 即便如此 volatile 的總開銷仍然要比鎖獲取低

volatile 操作不會像鎖一樣造成阻塞 因此 在能夠安全使用 volatile 的情況下 volatile 可以提供一些優於鎖的可伸縮特性 如果讀操作的次數要遠遠超過寫操作 與鎖相比 volatile 變數通常能夠減少同步的性能開銷

正確使用 volatile 的模式

很多並發性專家事實上往往引導用戶遠離 volatile 變數 因為使用它們要比使用鎖更加容易出錯 然而 如果謹慎地遵循一些良好定義的模式 就能夠在很多場合內安全地使用 volatile 變數 要始終牢記使用 volatile 的限制 只有在狀態真正獨立於程序內其他內容時才能使用 volatile 這條規則能夠避免將這些模式擴展到不安全的用例

模式 # :狀態標志

也許實現 volatile 變數的規范使用僅僅是使用一個布爾狀態標志 用於指示發生了一個重要的一次性事件 例如完成初始化或請求停機

很多應用程序包含了一種控制結構 形式為 在還沒有準備好停止程序時再執行一些工作 如清單 所示

清單 將 volatile 變數作為狀態標志使用

volatile boolean shutdownRequested;

public void shutdown() { shutdownRequested = true; }

public void doWork() {

while (!shutdownRequested) {

// do stuff

}

}

很可能會從循環外部調用 shutdown() 方法 即在另一個線程中 因此 需要執行某種同步來確保正確實現 shutdownRequested 變數的可見性 (可能會從 JMX 偵聽程序 GUI 事件線程中的操作偵聽程序 通過 RMI 通過一個 Web 服務等調用) 然而 使用 synchronized 塊編寫循環要比使用清單 所示的 volatile 狀態標志編寫麻煩很多 由於 volatile 簡化了編碼 並且狀態標志並不依賴於程序內任何其他狀態 因此此處非常適合使用 volatile

這種類型的狀態標記的一個公共特性是 通常只有一種狀態轉換 shutdownRequested 標志從 false 轉換為 true 然後程序停止 這種模式可以擴展到來回轉換的狀態標志 但是只有在轉換周期不被察覺的情況下才能擴展(從 false 到 true 再轉換到 false) 此外 還需要某些原子狀態轉換機制 例如原子變數

模式 # :一次性安全發布(one time safe publication)

缺乏同步會導致無法實現可見性 這使得確定何時寫入對象引用而不是原語值變得更加困難 在缺乏同步的情況下 可能會遇到某個對象引用的更新值(由另一個線程寫入)和該對象狀態的舊值同時存在 (這就是造成著名的雙重檢查鎖定(double checked locking)問題的根源 其中對象引用在沒有同步的情況下進行讀操作 產生的問題是您可能會看到一個更新的引用 但是仍然會通過該引用看到不完全構造的對象)

實現安全發布對象的一種技術就是將對象引用定義為 volatile 類型 清單 展示了一個示例 其中後台線程在啟動階段從資料庫載入一些數據 其他代碼在能夠利用這些數據時 在使用之前將檢查這些數據是否曾經發布過

清單 將 volatile 變數用於一次性安全發布

public class BackgroundFloobleLoader {

public volatile Flooble theFlooble;

public void initInBackground() {

// do lots of stuff

theFlooble = new Flooble() // this is the only write to theFlooble

}

}

public class SomeOtherClass {

public void doWork() {

while (true) {

// do some stuff…

// use the Flooble but only if it is ready

if (floobleLoader theFlooble != null)

doSomething(floobleLoader theFlooble)

}

}

}

如果 theFlooble 引用不是 volatile 類型 doWork() 中的代碼在解除對 theFlooble 的引用時 將會得到一個不完全構造的 Flooble

該模式的一個必要條件是 被發布的對象必須是線程安全的 或者是有效的不可變對象(有效不可變意味著對象的狀態在發布之後永遠不會被修改) volatile 類型的引用可以確保對象的發布形式的可見性 但是如果對象的狀態在發布後將發生更改 那麼就需要額外的同步

模式 # :獨立觀察(independent observation)

安全使用 volatile 的另一種簡單模式是 定期 發布 觀察結果供程序內部使用 例如 假設有一種環境感測器能夠感覺環境溫度 一個後台線程可能會每隔幾秒讀取一次該感測器 並更新包含當前文檔的 volatile 變數 然後 其他線程可以讀取這個變數 從而隨時能夠看到最新的溫度值

使用該模式的另一種應用程序就是收集程序的統計信息 清單 展示了身份驗證機制如何記憶最近一次登錄的用戶的名字 將反復使用 lastUser 引用來發布值 以供程序的其他部分使用

清單 將 volatile 變數用於多個獨立觀察結果的發布

public class UserManager {

public volatile String lastUser;

public boolean authenticate(String user String password) {

boolean valid = passwordIsValid(user password)

if (valid) {

User u = new User()

activeUsers add(u)

lastUser = user;

}

return valid;

}

}

該模式是前面模式的擴展 將某個值發布以在程序內的其他地方使用 但是與一次性事件的發布不同 這是一系列獨立事件 這個模式要求被發布的值是有效不可變的 即值的狀態在發布後不會更改 使用該值的代碼需要清楚該值可能隨時發生變化

模式 # : volatile bean 模式

volatile bean 模式適用於將 JavaBeans 作為 榮譽結構 使用的框架 在 volatile bean 模式中 JavaBean 被用作一組具有 getter 和/或 setter 方法 的獨立屬性的容器 volatile bean 模式的基本原理是 很多框架為易變數據的持有者(例如 HttpSession)提供了容器 但是放入這些容器中的對象必須是線程安全的

在 volatile bean 模式中 JavaBean 的所有數據成員都是 volatile 類型的 並且 getter 和 setter 方法必須非常普通 除了獲取或設置相應的屬性外 不能包含任何邏輯 此外 對於對象引用的數據成員 引用的對象必須是有效不可變的 (這將禁止具有數組值的屬性 因為當數組引用被聲明為 volatile 時 只有引用而不是數組本身具有 volatile 語義) 對於任何 volatile 變數 不變式或約束都不能包含 JavaBean 屬性 清單 中的示例展示了遵守 volatile bean 模式的 JavaBean:

清單 遵守 volatile bean 模式的 Person 對象

@ThreadSafe

public class Person {

private volatile String firstName;

private volatile String lastName;

private volatile int age;

public String getFirstName() { return firstName; }

public String getLastName() { return lastName; }

public int getAge() { return age; }

public void setFirstName(String firstName) {

this firstName = firstName;

}

public void setLastName(String lastName) {

this lastName = lastName;

}

public void setAge(int age) {

this age = age;

}

}

volatile 的高級模式

前面幾節介紹的模式涵蓋了大部分的基本用例 在這些模式中使用 volatile 非常有用並且簡單 這一節將介紹一種更加高級的模式 在該模式中 volatile 將提供性能或可伸縮性優勢

volatile 應用的的高級模式非常脆弱 因此 必須對假設的條件仔細證明 並且這些模式被嚴格地封裝了起來 因為即使非常小的更改也會損壞您的代碼!同樣 使用更高級的 volatile 用例的原因是它能夠提升性能 確保在開始應用高級模式之前 真正確定需要實現這種性能獲益 需要對這些模式進行權衡 放棄可讀性或可維護性來換取可能的性能收益 如果您不需要提升性能(或者不能夠通過一個嚴格的測試程序證明您需要它) 那麼這很可能是一次糟糕的交易 因為您很可能會得不償失 換來的東西要比放棄的東西價值更低

模式 # :開銷較低的讀 寫鎖策略

目前為止 您應該了解了 volatile 的功能還不足以實現計數器 因為 ++x 實際上是三種操作(讀 添加 存儲)的簡單組合 如果多個線程湊巧試圖同時對 volatile 計數器執行增量操作 那麼它的更新值有可能會丟失

然而 如果讀操作遠遠超過寫操作 您可以結合使用內部鎖和 volatile 變數來減少公共代碼路徑的開銷 清單 中顯示的線程安全的計數器使用 synchronized 確保增量操作是原子的 並使用 volatile 保證當前結果的可見性 如果更新不頻繁的話 該方法可實現更好的性能 因為讀路徑的開銷僅僅涉及 volatile 讀操作 這通常要優於一個無競爭的鎖獲取的開銷

清單 結合使用 volatile 和 synchronized 實現 開銷較低的讀 寫鎖

清單 結合使用 volatile 和 synchronized 實現 開銷較低的讀 寫鎖 單 結合使用 volatile 和 synchronized 實現 開銷較低的讀 寫鎖

@ThreadSafe

public class CheesyCounter {

// Employs the cheap read write lock trick

// All mutative operations MUST be done with the this lock held

@GuardedBy( this ) private volatile int value;

public int getValue() { return value; }

public synchronized int increment() {

return value++;

}

}

之所以將這種技術稱之為 開銷較低的讀 寫鎖 是因為您使用了不同的同步機制進行讀寫操作 因為本例中的寫操作違反了使用 volatile 的第一個條件 因此不能使用 volatile 安全地實現計數器 您必須使用鎖 然而 您可以在讀操作中使用 volatile 確保當前值的可見性 因此可以使用鎖進行所有變化的操作 使用 volatile 進行只讀操作 其中 鎖一次只允許一個線程訪問值 volatile 允許多個線程執行讀操作 因此當使用 volatile 保證讀代碼路徑時 要比使用鎖執行全部代碼路徑獲得更高的共享度 就像讀 寫操作一樣 然而 要隨時牢記這種模式的弱點 如果超越了該模式的最基本應用 結合這兩個競爭的同步機制將變得非常困難

結束語

lishixin/Article/program/Java/hx/201311/25585

2. java中線程同步的幾種方法

線程同步主要有以下種方法(示例中是實現計數的功能):

1、同步方法,即使用synchronized關鍵字修飾方法,例如:

publicsynchronizedvoidadd(intc){...}

2、同步代碼塊,即有synchronized關鍵字修飾的語句塊,例如:

publicvoidaddAndGet(intc){
synchronized(this){
count+=c;
}
}

3、使用特殊域變數(volatile)實現線程同步,該方法不能保證絕對的同步。

例如:privatevolatileintcount=0;

4、使用鎖實現線程同步,例如:

privateLocklock=newReentrantLock();
publicvoidadd(intc){
lock.lock();//上鎖
try{
count+=c;
}finally{
lock.unlock();//解鎖
}
}

5、使用原子變數實現線程同步,在java的util.concurrent.atomic包中提供了創建了原子類型變數的工具類,例如:

privateAtomicIntegercount=newAtomicInteger(1);
publicvoidadd(intc){
count.addAndGet(c);
}

6、使用局部變數實現線程同步,如果使用ThreadLocal管理變數,則每一個使用該變數的線程都獲得該變數的副本, 副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變數副本,而不會對其他線程產生影響。

ThreadLocal 類的常用方法

new ThreadLocal<T>() : 創建一個線程本地變數

get() : 返回此線程局部變數的當前線程副本中的值

initialValue() : 返回此線程局部變數的當前線程的"初始值"

set(T value) : 將此線程局部變數的當前線程副本中的值設置為value

示例代碼:

privatestaticThreadLocal<Integer>count=newThreadLocal<Integer>(){
@Override
protectedIntegerinitialValue(){
return1;
}
};

publicvoidadd(intc){
count.set(count.get()+c);
}

7、使用阻塞隊列實現,例如LinkedBlockingQueue,具體使用可網路LinkedBlockingQueue的用法或查看java文檔。

3. java 實現線程同步的方式有哪些

實現同步機制有兩個方法:
1、同步代碼塊:
synchronized(同一個數據){} 同一個數據:就是N條線程同時訪問一個數據。
2、同步方法:
public synchronized 數據返回類型 方法名(){}
就是使用 synchronized 來修飾某個方法,則該方法稱為同步方法。對於同步方法而言,無需顯示指定同步監視器,同步方法的同步監視器是 this 也就是該對象的本身(這里指的對象本身有點含糊,其實就是調用該同步方法的對象)通過使用同步方法,可非常方便的將某類變成線程安全的類,具有如下特徵:
1,該類的對象可以被多個線程安全的訪問。
2,每個線程調用該對象的任意方法之後,都將得到正確的結果。
3,每個線程調用該對象的任意方法之後,該對象狀態依然保持合理狀態。
註:synchronized關鍵字可以修飾方法,也可以修飾代碼塊,但不能修飾構造器,屬性等。
實現同步機制注意以下幾點: 安全性高,性能低,在多線程用。性能高,安全性低,在單線程用。
1,不要對線程安全類的所有方法都進行同步,只對那些會改變共享資源方法的進行同步。
2,如果可變類有兩種運行環境,當線程環境和多線程環境則應該為該可變類提供兩種版本:線程安全版本和線程不安全版本(沒有同步方法和同步塊)。在單線程中環境中,使用線程不安全版本以保證性能,在多線程中使用線程安全版本.

4. Java 線程同步幾種方式

(1)同步方法:
即有synchronized關鍵字修飾的方法。 由於java的每個對象都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用該方法前,需要獲得內置鎖,否則就處於阻塞狀態。
(2)同步代碼塊
即有synchronized關鍵字修飾的語句塊。被該關鍵字修飾的語句塊會自動被加上內置鎖,從而實現同步
(3)使用特殊域變數(Volatile)實現線程同步
a.volatile關鍵字為域變數的訪問提供了一種免鎖機制
b.使用volatile修飾域相當於告訴虛擬機該域可能會被其他線程更新
c.因此每次使用該域就要重新計算,而不是使用寄存器中的值
d.volatile不會提供任何原子操作,它也不能用來修飾final類型的變數
(4)使用重入鎖實現線程同步
在JavaSE5.0中新增了一個java.util.concurrent包來支持同步。ReentrantLock類是可重入、互斥、實現了Lock介面的鎖, 它與使用synchronized方法和快具有相同的基本行為和語義,並且擴展了其能力。
(5)使用局部變數實現線程同步

5. Java中線程怎麼同步

1、使用線程類自帶的join方法,將子線程加入到主線程,在子線程執行完之後,在執行主線程邏輯。

例如

[java]view plain

  • publicstaticvoidcallableDemo()

  • throwsInterruptedException

  • {

  • System.out.println("=========TestwithCallable=====");

  • List<Callable<Integer>>callList=newArrayList<Callable<Integer>>();

  • ExecutorServiceexec=Executors.newFixedThreadPool(2);

  • //採用匿名內部類實現

  • callList.add(newCallable<Integer>()

  • {

  • publicIntegercall()

  • throwsException

  • {

  • System.out.println("SubWorkerworker1doworkbeginat"+sdf.format(newDate()));

  • newThreadWaitDemo().doSomeWork();//做實際工作

  • System.out.println(""

  • +sdf.format(newDate()));

  • return0;

  • }

  • });

  • callList.add(newCallable<Integer>()

  • {

  • publicIntegercall()

  • throwsException

  • {

  • System.out.println("SubWorkerworker2doworkbeginat"+sdf.format(newDate()));

  • newThreadWaitDemo().doSomeWork();//做實際工作

  • System.out.println(""

  • +sdf.format(newDate()));

  • return0;

  • }

  • });

  • exec.invokeAll(callList);

  • exec.shutdown();

  • doSuperWork();

  • }

  • 5、這種過於惡心,只簡單說一下方法,主線程創建一個線程List,將每個子線程保存到列表中,然後定期輪詢列表中子線程狀態,當所有線程都完成之後,再執行主線程邏輯
  • 6. java多線程開發的同步機制有哪些

    Java同步
    標簽: 分類:

    一、關鍵字:

    thread(線程)、thread-safe(線程安全)、intercurrent(並發的)

    synchronized(同步的)、asynchronized(非同步的)、

    volatile(易變的)、atomic(原子的)、share(共享)

    二、總結背景:

    一次讀寫共享文件編寫,嚯,好傢伙,竟然揪出這些零碎而又是一路的知識點。於是乎,Google和翻閱了《Java參考大全》、《Effective Java Second Edition》,特此總結一下供日後工作學習參考。

    三、概念:

    1、 什麼時候必須同步?什麼叫同步?如何同步?

    要跨線程維護正確的可見性,只要在幾個線程之間共享非 final 變數,就必須使用 synchronized(或 volatile)以確保一個線程可以看見另一個線程做的更改。

    為了在線程之間進行可靠的通信,也為了互斥訪問,同步是必須的。這歸因於java語言規范的內存模型,它規定了:一個線程所做的變化何時以及如何變成對其它線程可見。

    因為多線程將非同步行為引進程序,所以在需要同步時,必須有一種方法強制進行。例如:如果2個線程想要通信並且要共享一個復雜的數據結構,如鏈表,此時需要
    確保它們互不沖突,也就是必須阻止B線程在A線程讀數據的過程中向鏈表裡面寫數據(A獲得了鎖,B必須等A釋放了該鎖)。

    為了達到這個目的,java在一個舊的的進程同步模型——監控器(Monitor)的基礎上實現了一個巧妙的方案:監控器是一個控制機制,可以認為是一個
    很小的、只能容納一個線程的盒子,一旦一個線程進入監控器,其它的線程必須等待,直到那個線程退出監控為止。通過這種方式,一個監控器可以保證共享資源在
    同一時刻只可被一個線程使用。這種方式稱之為同步。(一旦一個線程進入一個實例的任何同步方法,別的線程將不能進入該同一實例的其它同步方法,但是該實例
    的非同步方法仍然能夠被調用)。

    錯誤的理解:同步嘛,就是幾個線程可以同時進行訪問。

    同步和多線程關系:沒多線程環境就不需要同步;有多線程環境也不一定需要同步。

    鎖提供了兩種主要特性:互斥(mutual exclusion) 和可見性(visibility)。

    互斥即一次只允許一個線程持有某個特定的鎖,因此可使用該特性實現對共享數據的協調訪問協議,這樣,一次就只有一個線程能夠使用該共享數據。

    可見性要更加復雜一些,documents它必須確保釋放鎖之前對共享數據做出的更改對於隨後獲得該鎖的另一個線程是可見的 —— 如果沒有同步機制提供的這種可見性保證,線程看到的共享變數可能是修改前的值或不一致的值,這將引發許多嚴重問題

    小結:為了防止多個線程並發對同一數據的修改,所以需要同步,否則會造成數據不一致(就是所謂的:線程安全。如java集合框架中Hashtable和
    Vector是線程安全的。我們的大部分程序都不是線程安全的,因為沒有進行同步,而且我們沒有必要,因為大部分情況根本沒有多線程環境)。

    2、 什麼叫原子的(原子操作)?

    Java原子操作是指:不會被打斷地的操作。(就是做到互斥 和可見性?!)

    那難道原子操作就可以真的達到線程安全同步效果了嗎?實際上有一些原子操作不一定是線程安全的。

    那麼,原子操作在什麼情況下不是線程安全的呢?也許是這個原因導致的:java線程允許線程在自己的內存區保存變數的副本。允許線程使用本地的私有拷貝進
    行工作而非每次都使用主存的值是為了提高性能(本人愚見:雖然原子操作是線程安全的,可各線程在得到變數(讀操作)後,就是各自玩
    弄自己的副本了,更新操作(寫操作)因未寫入主存中,導致其它線程不可見)。

    那該如何解決呢?因此需要通過java同步機制。

    在java中,32位或者更少位數的賦值是原子的。在一個32位的硬體平台上,除了double和long型的其它原始類型通常都
    是使用32位進行表示,而double和long通常使用64位表示。另外,對象引用使用本機指針實現,通常也是32位的。對這些32位的類型的操作是原
    子的。

    這些原始類型通常使用32位或者64位表示,這又引入了另一個小小的神話:原始類型的大小是由語言保證的。這是不對的。java語言保證的是原始類型的表
    數范圍而非JVM中的存儲大小。因此,int型總是有相同的表數范圍。在一個JVM上可能使用32位實現,而在另一個JVM上可能是64位的。在此再次強
    調:在所有平台上被保證的是表數范圍,32位以及更小的值的操作是原子的。

    3、 不要搞混了:同步、非同步

    舉個例子:普通B/S模式(同步)AJAX技術(非同步)

    同步:提交請求->等待伺服器處理->處理完返回 這個期間客戶端瀏覽器不能幹任何事

    非同步:請求通過事件觸發->伺服器處理(這是瀏覽器仍然可以作其他事情)->處理完畢

    可見,彼「同步」非此「同步」——我們說的java中的那個共享數據同步(synchronized)

    一個同步的對象是指行為(動作),一個是同步的對象是指物質(共享數據)。

    4、 Java同步機制有4種實現方式:(部分引用網上資源)

    ① ThreadLocal ② synchronized( ) ③ wait() 與 notify() ④ volatile

    目的:都是為了解決多線程中的對同一變數的訪問沖突
    ThreadLocal
    ThreadLocal 保證不同線程擁有不同實例,相同線程一定擁有相同的實例,即為每一個使用該變數的線程提供一個該變數值的副本,每一個線程都可以獨立改變自己的副本,而不是與其它線程的副本沖突。

    優勢:提供了線程安全的共享對象

    與其它同步機制的區別:同步機制是為了同步多個線程對相同資源的並發訪問,是為了多個線程之間進行通信;而 ThreadLocal 是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源,這樣當然不需要多個線程進行同步了。

    volatile
    volatile 修飾的成員變數在每次被線程訪問時,都強迫從共享內存中重讀該成員變數的值。而且,當成員變數發生變化時,強迫線程將變化值回寫到共享內存。
    優勢:這樣在任何時刻,兩個不同的線程總是看到某個成員變數的同一個值。
    緣由:Java
    語言規范中指出,為了獲得最佳速度,允許線程保存共享成員變數的私有拷貝,而且只當線程進入或者離開同步代碼塊時才與共享成員變數的原
    始值對比。這樣當多個線程同時與某個對象交互時,就必須要注意到要讓線程及時的得到共享成員變數的變化。而 volatile
    關鍵字就是提示 VM :對於這個成員變數不能保存它的私有拷貝,而應直接與共享成員變數交互。
    使用技巧:在兩個或者更多的線程訪問的成員變數上使用 volatile 。當要訪問的變數已在 synchronized 代碼塊中,或者為常量時,不必使用。

    線程為了提高效率,將某成員變數(如A)拷貝了一份(如B),線程中對A的訪問其實訪問的是B。只在某些動作時才進行A和B的同步,因此存在A和B不一致
    的情況。volatile就是用來避免這種情況的。
    volatile告訴jvm,它所修飾的變數不保留拷貝,直接訪問主內存中的(讀操作多時使用較好;線程間需要通信,本條做不到)

    Volatile 變數具有 synchronized 的可見性特性,但是不具備原子特性。這就是說線程能夠自動發現 volatile
    變數的最新值。Volatile
    變數可用於提供線程安全,但是只能應用於非常有限的一組用例:多個變數之間或者某個變數的當前值與修改後值
    之間沒有約束。

    您只能在有限的一些情形下使用 volatile 變數替代鎖。要使 volatile 變數提供理想的線程安全,必須同時滿足下面兩個條件:

    對變數的寫操作不依賴於當前值;該變數沒有包含在具有其他變數的不變式中。

    sleep() vs wait()
    sleep是線程類(Thread)的方法,導致此線程暫停執行指定時間,把執行機會給其他線程,但是監控狀態依然保持,到時後會自動恢復。調用sleep不會釋放對象鎖。
    wait是Object類的方法,對此對象調用wait方法導致本線程放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象發出notify方法(或notifyAll)後本線程才進入對象鎖定池准備獲得對象鎖進入運行狀態。

    (如果變數被聲明為volatile,在每次訪問時都會和主存一致;如果變數在同步方法或者同步塊中被訪問,當在方法或者塊的入口處獲得鎖以及方法或者塊退出時釋放鎖時變數被同步。)

    7. JAVA中線程同步方法有哪些

    JAVA中線程同步方法一般有以下三種:
    1 wait方法:
    該方法屬於Object的方法,wait方法的作用是使得當前調用wait方法所在部分(代碼塊)的線程停止執行,並釋放當前獲得的調用wait所在的代碼塊的鎖,並在其他線程調用notify或者notifyAll方法時恢復到競爭鎖狀態(一旦獲得鎖就恢復執行)。
    調用wait方法需要注意幾點:
    第一點:wait被調用的時候必須在擁有鎖(即synchronized修飾的)的代碼塊中。
    第二點:恢復執行後,從wait的下一條語句開始執行,因而wait方法總是應當在while循環中調用,以免出現恢復執行後繼續執行的條件不滿足卻繼續執行的情況。
    第三點:若wait方法參數中帶時間,則除了notify和notifyAll被調用能激活處於wait狀態(等待狀態)的線程進入鎖競爭外,在其他線程中interrupt它或者參數時間到了之後,該線程也將被激活到競爭狀態。
    第四點:wait方法被調用的線程必須獲得之前執行到wait時釋放掉的鎖重新獲得才能夠恢復執行。
    2 notify方法和notifyAll方法:
    notify方法通知調用了wait方法,但是尚未激活的一個線程進入線程調度隊列(即進入鎖競爭),注意不是立即執行。並且具體是哪一個線程不能保證。另外一點就是被喚醒的這個線程一定是在等待wait所釋放的鎖。
    notifyAll方法則喚醒所有調用了wait方法,尚未激活的進程進入競爭隊列。
    3 synchronized關鍵字:
    第一點:synchronized用來標識一個普通方法時,表示一個線程要執行該方法,必須取得該方法所在的對象的鎖。
    第二點:synchronized用來標識一個靜態方法時,表示一個線程要執行該方法,必須獲得該方法所在的類的類鎖。
    第三點:synchronized修飾一個代碼塊。類似這樣:synchronized(obj) { //code.... }。表示一個線程要執行該代碼塊,必須獲得obj的鎖。這樣做的目的是減小鎖的粒度,保證當不同塊所需的鎖不沖突時不用對整個對象加鎖。利用零長度的byte數組對象做obj非常經濟。

    閱讀全文

    與java中的線程同步相關的資料

    熱點內容
    程序員qq表白代碼編輯 瀏覽:888
    聯想伺服器怎麼進後台 瀏覽:112
    安卓定製rom怎麼刷 瀏覽:537
    三層交換機的配置命令 瀏覽:108
    49演算法公式 瀏覽:788
    求最小生成樹演算法代碼及運行圖片 瀏覽:930
    python掃雷計數 瀏覽:879
    什麼安卓手機品牌最保值 瀏覽:843
    編程貓買房子 瀏覽:134
    c語言系列編程 瀏覽:742
    符合國標加密標准技術 瀏覽:496
    加密狗介面會壞嗎 瀏覽:625
    javame開發 瀏覽:380
    python3偽裝瀏覽器 瀏覽:242
    信息聯想伺服器專班是干什麼的 瀏覽:99
    python獲取cpu個數 瀏覽:864
    命令提示符查網速 瀏覽:229
    對於某個理論演算法可以直接抄嗎 瀏覽:188
    如何訪問ftp伺服器下載文件 瀏覽:392
    呼蘭程序員吐槽剪輯 瀏覽:493