- 隨著多程式多執行緒的出現,對共享資源(裝置,資料等)的競爭往往會導致資源的使用表現為隨機無序
- 例如:一個執行緒想在控制檯輸出"I am fine",剛寫到"I am",就被另一執行緒搶佔控制檯輸出"naughty",導致結果是"I am naughty";對於資源的被搶佔使用,我們能怎麼辦呢?當然不是涼拌,可使用鎖進行同步管理,使得資源在加鎖期間,其他執行緒不可搶佔使用
1 鎖的分類
悲觀鎖
- 悲觀鎖,每次去請求資料的時候,都認為資料會被搶佔更新(悲觀的想法);所以每次運算元據時都要先加上鎖,其他執行緒修改資料時就要等待獲取鎖。適用於寫多讀少的場景,synchronized就是一種悲觀鎖
樂觀鎖
- 在請求資料時,覺得無人搶佔修改。等真正更新資料時,才判斷此期間別人有沒有修改過(預先讀出一個版本號或者更新時間戳,更新時判斷是否變化,沒變則期間無人修改);和悲觀鎖不同的是,期間資料允許其他執行緒修改
自旋鎖
- 一句話,魔力轉轉圈。當嘗試給資源加鎖卻被其他執行緒先鎖定時,不是阻塞等待而是迴圈再次加鎖
- 在鎖常被短暫持有的場景下,執行緒阻塞掛起導致CPU上下文頻繁切換,這可用自旋鎖解決;但自旋期間它佔用CPU空轉,因此不適用長時間持有鎖的場景
2 synchronized底層原理
- 程式碼使用synchronized加鎖,在編譯之後的位元組碼是怎樣的呢
public class Test {
public static void main(String[] args){
synchronized(Test.class){
System.out.println("hello");
}
}
}
擷取部分位元組碼,如下
4: monitorenter
5: getstatic #9 // Field java/lang/System.out:Ljava/io/PrintStream;
8: ldc #15 // String hello
10: invokevirtual #17 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
13: aload_1
14: monitorexit
位元組碼出現了4: monitorenter和14: monitorexit兩個指令;字面理解就是監視進入,監視退出。可以理解為程式碼塊執行前的加鎖,和退出同步時的解鎖
- 那monitorenter和monitorexit,又揹著我們幹了啥呢?
- 執行monitorenter指令時,執行緒會為鎖物件關聯一個ObjectMonitor物件
objectMonitor.cpp
ObjectMonitor() {
_header = NULL;
_count = 0; \\用來記錄該執行緒獲取鎖的次數
_waiters = 0,
_recursions = 0; \\鎖的重入次數
_object = NULL;
_owner = NULL; \\當前持有ObjectMonitor的執行緒
_WaitSet = NULL; \\wait()方法呼叫後的執行緒等待佇列
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; \\阻塞等待佇列
FreeNext = NULL ;
_EntryList = NULL ; \\synchronized 進來執行緒的排隊佇列
_SpinFreq = 0 ;
_SpinClock = 0 ; \\自旋計算
OwnerIsThread = 0 ;
}
- 每個執行緒都有兩個ObjectMonitor物件列表,分別為free和used列表,如果當前free列表為空,執行緒將向全域性global list請求分配ObjectMonitor
- ObjectMonitor的owner、WaitSet、Cxq、EntryList這幾個屬性比較關鍵。WaitSet、Cxq、EntryList的佇列元素是包裝執行緒後的物件-ObjectWaiter;而獲取owner的執行緒,既為獲得鎖的執行緒
- monitorenter對應的執行方法
void ATTR ObjectMonitor::enter(TRAPS) {
...
//獲取鎖:cmpxchg_ptr原子操作,嘗試將_owner替換為自己,並返回舊值
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
...
// 重複獲取鎖,次數加1,返回
if (cur == Self) {
_recursions ++ ;
return ;
}
//首次獲取鎖情況處理
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
_recursions = 1 ;
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
...
//嘗試自旋獲取鎖
if (Knob_SpinEarly && TrySpin (Self) > 0) {
...
- monitorexit對應的執行方法
void ATTR ObjectMonitor::exit(TRAPS)...
程式碼太長,就不貼了。主要是recursions減1、count減少1或者如果執行緒不再持有owner(非重入加鎖)則設定owner為null,退鎖的持有狀態,並喚醒Cxq佇列的執行緒
總結
- 執行緒遇到synchronized同步時,先會進入EntryList佇列中,然後嘗試把owner變數設定為當前執行緒,同時monitor中的計數器count加1,即獲得物件鎖。否則通過嘗試自旋一定次數加鎖,失敗則進入Cxq佇列阻塞等待
- 執行緒執行完畢將釋放持有的owner,owner變數恢復為null,count自減1,以便其他執行緒進入獲取鎖
- synchronized修飾方法原理也是類似的。只不過沒用monitor指令,而是使用ACC_SYNCHRONIZED標識方法的同步
public synchronized void lock(){
System.out.println("world");
}
....
public synchronized void lock();
descriptor: ()V
flags: (0x0029) ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #20 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #26 // String world
5: invokevirtual #28 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- synchronized是可重入,非公平鎖,因為entryList的執行緒會先自旋嘗試加鎖,而不是加入cxq排隊等待,不公平
3 Object的wait和notify方法原理
- wait,notify必須是持有當前物件鎖Monitor的執行緒才能呼叫 (物件鎖代指ObjectMonitor/Monitor,鎖物件代指Object)
- 上面有說到,當在sychronized中鎖物件Object呼叫wait時會加入waitSet佇列,WaitSet的元素物件就是ObjectWaiter
class ObjectWaiter : public StackObj {
public:
enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;
enum Sorted { PREPEND, APPEND, SORTED } ;
ObjectWaiter * volatile _next;
ObjectWaiter * volatile _prev;
Thread* _thread;
ParkEvent * _event;
volatile int _notified ;
volatile TStates TState ;
Sorted _Sorted ; // List placement disposition
bool _active ; // Contention monitoring is enabled
public:
ObjectWaiter(Thread* thread);
void wait_reenter_begin(ObjectMonitor *mon);
void wait_reenter_end(ObjectMonitor *mon);
};
呼叫物件鎖的wait()方法時,執行緒會被封裝成ObjectWaiter,最後使用park方法掛起
//objectMonitor.cpp
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS){
...
//執行緒封裝成 ObjectWaiter物件
ObjectWaiter node(Self);
node.TState = ObjectWaiter::TS_WAIT ;
...
//一系列判斷操作,當執行緒確實加入WaitSet時,則使用park方法掛起
if (node._notified == 0) {
if (millis <= 0) {
Self->_ParkEvent->park () ;
} else {
ret = Self->_ParkEvent->park (millis) ;
}
}
而當物件鎖使用notify()時
- 如果waitSet為空,則直接返回
- waitSet不為空從waitSet獲取一個ObjectWaiter,然後根據不同的Policy加入到EntryList或通過
Atomic::cmpxchg_ptr
指令自旋操作加入cxq佇列或者直接unpark喚醒
void ObjectMonitor::notify(TRAPS){
CHECK_OWNER();
//waitSet為空,則直接返回
if (_WaitSet == NULL) {
TEVENT (Empty-Notify) ;
return ;
}
...
//通過DequeueWaiter獲取_WaitSet列表中的第一個ObjectWaiter
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ;
ObjectWaiter * iterator = DequeueWaiter() ;
if (iterator != NULL) {
....
if (Policy == 2) { // prepend to cxq
// prepend to cxq
if (List == NULL) {
iterator->_next = iterator->_prev = NULL ;
_EntryList = iterator ;
} else {
iterator->TState = ObjectWaiter::TS_CXQ ;
for (;;) {
ObjectWaiter * Front = _cxq ;
iterator->_next = Front ;
if (Atomic::cmpxchg_ptr (iterator, &_cxq, Front) == Front) {
break ;
}
}
}
}
- Object的notifyAll方法則對應
voidObjectMonitor::notifyAll(TRAPS)
,流程和notify類似。不過會通過for迴圈取出WaitSet的ObjectWaiter節點,再依次喚醒所有執行緒
4 jvm對synchronized的優化
- 先介紹下32位JVM下JAVA物件頭的結構
偏向鎖
- 未加鎖的時候,鎖標誌為01,包含雜湊值、年齡分代和偏向鎖標誌位(0)
- 施加偏向鎖時,雜湊值和一部分無用記憶體會轉化為鎖主人的執行緒資訊,以及加鎖時的時間戳epoch,此時鎖標誌位沒變,偏向鎖標誌改為1
- 加鎖時先判斷當前執行緒id是否與MarkWord的執行緒id是否一致,一致則執行同步程式碼;不一致則檢查偏向標誌是否偏向,未偏向則使用CAS加鎖;未偏向CAS加鎖失敗和存在偏向鎖會導致偏向鎖膨脹為輕量級鎖,或者重新偏向
- 偏向鎖只有遇到其他執行緒競爭偏向鎖時,持有偏向鎖的執行緒才會釋放鎖,執行緒不會主動去釋放偏向鎖
輕量級鎖
- 當發生多個執行緒競爭時,偏向鎖會變為輕量級鎖,鎖標誌位為00
- 獲得鎖的執行緒會先將偏向鎖撤銷(在安全點),並在棧楨中建立鎖記錄LockRecord,物件的MarkWord被複制到剛建立的LockRecord,然後CAS嘗試將記錄LockRecord的owner指向鎖物件,再將鎖物件的MarkWord指向鎖,加鎖成功
- 如果CAS加鎖失敗,執行緒會自旋一定次數加鎖,再失敗則升級為重量級鎖
重量級鎖
- 重量級鎖就是上面介紹到synchronized使用監視器Monitor實現的鎖機制
- 競爭執行緒激烈,鎖則繼續膨脹,變為重量級鎖,也是互斥鎖,鎖標誌位為10,MarkWord其餘內容被替換為一個指向物件鎖Monitor的指標
自旋鎖
- 減少不必要的CPU上下文切換;在輕量級鎖升級為重量級鎖時,就使用了自旋加鎖的方式
鎖粗化
- 多次加鎖操作在JVM內部也是種消耗,如果多個加鎖可以合併為一個鎖,就可減少不必要的開銷
Test.class
//編譯器會考慮將兩次加鎖合併
public void test(){
synchronized(this){
System.out.println("hello");
}
synchronized(this){
System.out.println("world");
}
}
鎖消除
- 刪除不必要的加鎖操作,如果變數是獨屬一個執行緒的棧變數,加不加鎖都是安全的,編譯器會嘗試消除鎖
- 開啟鎖消除需要在JVM引數上設定
-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
//StringBuffer的append操作會加上synchronized,
//但是變數buf不加鎖也安全的,編譯器會把鎖消除
public void test() {
StringBuffer buf = new StringBuffer();
buf.append("hello").append("world");
}
其他鎖優化方法
- 分段鎖,分段鎖也並非一種實際的鎖,而是一種思想;ConcurrentHashMap是學習分段鎖的最好實踐。主要是將大物件拆成小物件,然後對大物件的加鎖操作變成對小物件加鎖,增加了並行度
5 CAS的底層原理
- 在
volatile int i = 0; i++
中,volatile型別的讀寫是原子同步的,但是i++卻不能保證同步性,我們該怎麼呢? - 可以使用synchronized加鎖;還有就是用CAS(比較並交換),使用樂觀鎖的思想同步,先判斷共享變數是否改變,沒有則更新。下面看看不同步版本的CAS
int expectedValue = 1;
public boolean compareAndSet(int newValue) {
if(expectedValue == 1){
expectedValue = newValue;
return ture;
}
return false;
}
在jdk是有提供同步版的CAS解決方案,其中使用了UnSafe.java的底層方法
//UnSafe.java
@HotSpotIntrinsicCandidate
public final native boolean compareAndSetInt(Object o, long offset, int expected, int x) ..
@HotSpotIntrinsicCandidate
public final native int compareAndExchangeInt(Object o, long offset, int expected, int x)...
我們再來看看本地方法,Unsafe.cpp中的compareAndSwapInt
//unsafe.cpp
UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
UnsafeWrapper("Unsafe_CompareAndSwapInt");
oop p = JNIHandles::resolve(obj);
jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
UNSAFE_END
在Linux的x86,Atomic::cmpxchg方法的實現如下
/**
1 __asm__表示彙編的開始;
2 volatile表示禁止編譯器優化;//禁止指令重排
3 LOCK_IF_MP是個行內函數,
根據當前系統是否為多核處理器,
決定是否為cmpxchg指令新增lock字首 //記憶體屏障
*/
inline jint Atomic::cmpxchg (jint exchange_value, volatile jint* dest, jint compare_value) {
int mp = os::is_MP();
__asm__ volatile (LOCK_IF_MP(%4) "cmpxchgl %1,(%3)"
: "=a" (exchange_value)
: "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
: "cc", "memory");
return exchange_value;
}
到這一步,可以總結到:jdk提供的CAS機制,在彙編層級,會禁止變數兩側的指令優化,然後使用cmpxchg指令比較並更新變數值(原子性),如果是多核則使用lock鎖定(快取鎖、MESI)
6 CAS同步操作的問題
ABA問題
- 執行緒X準備將變數的值從A改為B,然而這期間執行緒Y將變數的值從A改為C,然後再改為A;最後執行緒X檢測變數值是A,並置換為B。但實際上,A已經不再是原來的A了
- 解決方法,是把變數定為唯一型別。值可以加上版本號,或者時間戳。如加上版本號,執行緒Y的修改變為A1->B2->A3,此時執行緒X再更新則可以判斷出A1不等於A3
只能保證一個共享變數的原子操作
- 只保證一個共享變數的原子操作,對多個共享變數同步時,迴圈CAS是無法保證操作的原子
7 基於volatile + CAS 實現同步鎖的原理
- CAS只能同步一個變數的修改,我們又應該如何用它來鎖住程式碼塊呢?
先說說實現鎖的要素
- 1 同步程式碼塊同一時刻只能有一個執行緒能執行
- 2 加鎖操作要happens-before同步程式碼塊裡的操作,而程式碼塊裡的操作要happens-before解鎖操作
- 3 同步程式碼塊結束後相對其他執行緒其修改的變數是可見的 (記憶體可見性)
要素1:可以利用CAS的原子性來實現,任意時刻只有一個執行緒能成功操作變數
- 先設想CAS操作的共享變數是一個關聯程式碼塊的同步狀態變數,同步開始之前先CAS更新狀態變數為加鎖狀態,同步結束之後,再CAS狀態變數為無鎖狀態
- 如果期間有第二個執行緒來加鎖,則會發現狀態變數為加鎖狀態,則放棄執行同步程式碼塊
要素2:使用volatile修飾狀態變數,禁止指令重排
- volatile保證同步程式碼裡的操作happens-before解鎖操作,而加鎖操作happens-before程式碼塊裡的操作
要素3:還是用volatile,volatile變數寫指令前後會插入記憶體屏障
- volatile修飾的狀態變數被CAS為無鎖狀態前,同步程式碼塊的髒資料就會被更新,被各個執行緒可見
//虛擬碼
volatile state = 0 ; // 0-無鎖 1-加鎖;volatile禁止指令重排,加入記憶體屏障
...
if(cas(state, 0 , 1)){ // 1 加鎖成功,只有一個執行緒能成功加鎖
... // 2 同步程式碼塊
cas(state, 1, 0); // 3 解鎖時2的操作具有可見性
}
8 LockSupport瞭解一下
- LockSupport是基於Unsafe類,由JDK提供的執行緒操作工具類,主要作用就是掛起執行緒,喚醒執行緒。Unsafe.park,unpark操作時,會呼叫當前執行緒的變數parker代理執行。Parker程式碼
JavaThread* thread=JavaThread::thread_from_jni_environment(env);
...
thread->parker()->park(isAbsolute != 0, time);
class PlatformParker : public CHeapObj {
protected:
//互斥變數型別
pthread_mutex_t _mutex [1] ;
//條件變數型別
pthread_cond_t _cond [1] ;
...
}
class Parker : public os::PlatformParker {
private:
volatile int _counter ;
...
public:
void park(bool isAbsolute, jlong time);
void unpark();
...
}
- 在Linux系統下,用的POSIX執行緒庫pthread中的mutex(互斥量),condition來實現執行緒的掛起、喚醒
- 注意點:當park時,counter變數被設定為0,當unpark時,這個變數被設定為1
unpark和park執行順序不同時,counter和cond的狀態變化如下
- 先park後unpark; park:counter值不變,但會設定一個cond; unpark:counter先加1,檢查cond存在,counter減為0
- 先unpark後park;park:counter變為1,但不設定cond;unpark:counter減為0(執行緒不會因為park掛起)
- 先多次unpark;counter也只設定為為1
9 LockSupport.park和Object.wait區別
- 兩種方式都有具有掛起的執行緒的能力
- 執行緒在Object.wait之後必須等到Object.notify才能喚醒
- LockSupport可以先unpark執行緒,等執行緒執行LockSupport.park是不會掛起的,可以繼續執行
- 需要注意的是就算執行緒多次unpark;也只能讓執行緒第一次park是不會掛起
10 AbstractQueuedSynchronizer(AQS)
- AQS其實就是基於volatile+cas實現的鎖模板;如果需要執行緒阻塞等待,喚醒機制,則使用LockSupport掛起、喚醒執行緒
//AbstractQueuedSynchronizer.java
public class AbstractQueuedSynchronizer{
//執行緒節點
static final class Node {
...
volatile Node prev;
volatile Node next;
volatile Thread thread;
...
}
....
//head 等待佇列頭尾節點
private transient volatile Node head;
private transient volatile Node tail;
// The synchronization state. 同步狀態
private volatile int state;
...
//提供CAS操作,狀態具體的修改由子類實現
protected final boolean compareAndSetState(int expect, int update) {
return STATE.compareAndSet(this, expect, update);
}
}
- AQS內部維護一個同步佇列,元素就是包裝了執行緒的Node
- 同步佇列中首節點是獲取到鎖的節點,它在釋放鎖的時會喚醒後繼節點,後繼節點獲取到鎖的時候,會把自己設為首節點
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
- 執行緒會先嚐試獲取鎖,失敗則封裝成Node,CAS加入同步佇列的尾部。在加入同步佇列的尾部時,會判斷前驅節點是否是head結點,並嘗試加鎖(可能前驅節點剛好釋放鎖),否則執行緒進入阻塞等待
在AQS還存一個ConditionObject的內部類,它的使用機制和Object.wait、notify類似
//AbstractQueuedSynchronizer.java
public class ConditionObject implements Condition, java.io.Serializable {
//條件佇列;Node 複用了AQS中定義的Node
private transient Node firstWaiter;
private transient Node lastWaiter;
...
- 每個Condition物件內部包含一個Node元素的FIFO條件佇列
- 當一個執行緒呼叫Condition.await()方法,那麼該執行緒將會釋放鎖、構造Node加入條件佇列並進入等待狀態
//類似Object.wait
public final void await() throws InterruptedException{
...
Node node = addConditionWaiter(); //構造Node,加入條件佇列
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
//掛起執行緒
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//notify喚醒執行緒後,加入同步佇列繼續競爭鎖
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
- 呼叫Condition.signal時,獲取條件佇列的首節點,將其移動到同步佇列並且利用LockSupport喚醒節點中的執行緒。隨後繼續執行wait掛起前的狀態,呼叫acquireQueued(node, savedState)競爭同步狀態
//類似Object.notify
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
- volatile+cas機制保證了程式碼的同步性和可見性,而AQS封裝了執行緒阻塞等待掛起,解鎖喚醒其他執行緒的邏輯。AQS子類只需根據狀態變數,判斷是否可獲取鎖,是否釋放鎖成功即可
- 繼承AQS需要選性重寫以下幾個介面
protected boolean tryAcquire(int arg);//嘗試獨佔性加鎖
protected boolean tryRelease(int arg);//對應tryAcquire釋放鎖
protected int tryAcquireShared(int arg);//嘗試共享性加鎖
protected boolean tryReleaseShared(int arg);//對應tryAcquireShared釋放鎖
protected boolean isHeldExclusively();//該執行緒是否正在獨佔資源,只有用到condition才需要取實現它
11 ReentrantLock的原理
- ReentrantLock實現了Lock介面,並使用內部類Sync(Sync繼承AbstractQueuedSynchronizer)來實現同步操作
- ReentrantLock內部類Sync
abstract static class Sync extends AbstractQueuedSynchronizer{
....
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//直接CAS狀態加鎖,非公平操作
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
...
//重寫了tryRelease
protected final boolean tryRelease(int releases) {
c = state - releases; //改變同步狀態
...
//修改volatile 修飾的狀態變數
setState(c);
return free;
}
}
- Sync的子類NonfairSync和FairSync都重寫了tryAcquire方法
- 其中NonfairSync的tryAcquire呼叫父類的nonfairTryAcquire方法, FairSync則自己重寫tryAcquire的邏輯。其中呼叫hasQueuedPredecessors()判斷是否有排隊Node,存在則返回false(false會導致當前執行緒排隊等待鎖)
static final class NonfairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
....
static final class FairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
....
12 AQS排他鎖的例項demo
public class TwinsLock implements Lock {
private final Sync sync = new Sync(2);
@Override
public void lockInterruptibly() throws InterruptedException { throw new RuntimeException(""); }
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {throw new RuntimeException("");}
@Override
public Condition newCondition() { return sync.newCondition(); }
@Override
public void lock() { sync.acquireShared(1); }
@Override
public void unlock() { sync.releaseShared(1); } }
@Override
public boolean tryLock() { return sync.tryAcquireShared(1) > -1; }
}
再來看看Sync的程式碼
class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
if (count <= 0) {
throw new IllegalArgumentException("count must large than zero");
}
setState(count);
}
@Override
public int tryAcquireShared(int reduceCount) {
for (; ; ) {
int current = getState();
int newCount = current - reduceCount;
if (newCount < 0 || compareAndSetState(current, newCount)) {
return newCount;
}
}
}
@Override
public boolean tryReleaseShared(int returnCount) {
for (; ; ) {
int current = getState();
int newCount = current + returnCount;
if (compareAndSetState(current, newCount)) {
return true;
}
}
}
public Condition newCondition() {
return new AbstractQueuedSynchronizer.ConditionObject();
}
}
13 使用鎖,能防止執行緒死迴圈嗎
- 答案是不一定的;對於單個資源來說是可以做的;但是多個資源會存在死鎖的情況,例如執行緒A持有資源X,等待資源Y,而執行緒B持有資源Y,等待資源X
- 有了鎖,可以對資源加狀態控制,但是我們還需要防止死鎖的產生,打破產生死鎖的四個條件之一就行
- 1 資源不可重複被兩個及以上的使用者佔用
- 2 使用者持有資源並等待其他資源
- 3 資源不可被搶佔
- 4 多個使用者形成等待對方資源的迴圈圈
14 ThreadLocal是否可保證資源的同步
- 當使用ThreadLocal宣告變數時,ThreadLocal為每個使用該變數的執行緒提供獨立的變數副本,每一個執行緒都可以獨立地改變自己的副本,而不會影響其它執行緒所對應的副本
- 從上面的概念可知,ThreadLocal其實並不能保證變數的同步性,只是給每一個執行緒分配一個變數副本