十六、併發程式設計之讀寫鎖認識與原理
一、讀寫鎖認識
- 排他鎖(寫):在同一時刻只有一個執行緒可以進入
- 共享鎖(讀):在同一時刻可以有多個執行緒同時進入
package com.roocon.thread.ta4;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Demo {
private Map<String,Object> map = new HashMap<>();
private ReadWriteLock rwl = new ReentrantReadWriteLock();//建立讀寫鎖
private Lock r = rwl.readLock();//拿到讀鎖
private Lock w = rwl.writeLock();//拿到寫鎖
//讀操作
public Object get(String key) {
r.lock();
System.out.println(Thread. currentThread().getName()+"讀操作在執行。。。");
try {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return map.get(key);
}finally {
r.unlock();
System.out.println(Thread.currentThread().getName()+"讀操作執行完畢");
}
}
//寫操作(寫和寫互斥,寫和讀互斥,讀和讀共享)
public void put(String key,Object value) {
w.lock();
System.out.println(Thread.currentThread().getName()+"寫操作在執行。。。");
try {
Thread.sleep(30);
map.put(key, value);
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
w.unlock();
System.out.println(Thread.currentThread().getName()+"寫操作執行完畢");
}
}
public static void main(String[] args) {
Demo d = new Demo();
//執行緒1
new Thread(new Runnable() {
@Override
public void run() {
//d.put("key1","value1");
System.out.println(d.get("key4"));
}
}).start();
//執行緒2
new Thread(new Runnable() {
@Override
public void run() {
//d.put("key2","value2");
System.out.println(d.get("key1"));
}
}).start();
//執行緒3
new Thread(new Runnable() {
@Override
public void run() {
//d.put("key3","value3");
System.out.println(d.get("key2"));
}
}).start();
//執行緒4
new Thread(new Runnable() {
@Override
public void run() {
//d.put("key4","value4");
System.out.println(d.get("key3"));
}
}).start();
}
}
二、讀寫鎖簡介
現實中有這樣一種場景:對共享資源有讀和寫的操作,且寫操作沒有讀操作那麼頻繁。在沒有寫操作的時候,多個執行緒同時讀一個資源沒有任何問題,所以應該允許多個執行緒同時讀取共享資源;但是如果一個執行緒想去寫這些共享資源,就不應該允許其他執行緒對該資源進行讀和寫的操作了。
針對這種場景,JAVA的併發包提供了讀寫鎖ReentrantReadWriteLock,它表示兩個鎖,一個是讀操作相關的鎖,稱為共享鎖;一個是寫相關的鎖,稱為排他鎖,描述如下:
1、執行緒進入讀鎖的前提條件:
- 沒有其他執行緒的寫鎖,
- 沒有寫請求或者有寫請求,但呼叫執行緒和持有鎖的執行緒是同一個。
2、執行緒進入寫鎖的前提條件:
- 沒有其他執行緒的讀鎖
- 沒有其他執行緒的寫鎖
3、而讀寫鎖有以下三個重要的特性:
- 公平選擇性:支援非公平(預設)和公平的鎖獲取方式,吞吐量還是非公平優於公平。
- 重進入:讀鎖和寫鎖都支援執行緒重進入。
- 鎖降級:遵循獲取寫鎖、獲取讀鎖再釋放寫鎖的次序,寫鎖能夠降級成為讀鎖。
4.讀寫鎖需要儲存的狀態
- 寫鎖重入的次數
- 讀鎖的個數
- 每個讀鎖重入的次數
三、原始碼解讀
1、ReentrantReadWriteLock 類的整體結構:
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
/** 讀鎖 */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** 寫鎖 */
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;//同步器
/** 使用預設(非公平)的排序屬性建立一個新的 ReentrantReadWriteLock */
public ReentrantReadWriteLock() {
this(false);
}
/** 使用給定的公平策略建立一個新的 ReentrantReadWriteLock */
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
/** 返回用於寫入操作的鎖 */
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
/** 返回用於讀取操作的鎖 */
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class NonfairSync extends Sync {}
static final class FairSync extends Sync {}
public static class ReadLock implements Lock, java.io.Serializable {}
public static class WriteLock implements Lock, java.io.Serializable {}
}
- 1.類的繼承關係
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {}
說明:可以看到,ReentrantReadWriteLock實現了ReadWriteLock介面,ReadWriteLock介面定義了獲取讀鎖和寫鎖的規範,具體需要實現類去實現;同時其還實現了Serializable介面,表示可以進行序列化,在原始碼中可以看到ReentrantReadWriteLock實現了自己的序列化邏輯。
- 2.類的內部類
ReentrantReadWriteLock有五個內部類,五個內部類之間也是相互關聯的。內部類的關係如下圖所示。
說明:如上圖所示,Sync繼承自AQS、NonfairSync繼承自Sync類、FairSync繼承自Sync類(通過建構函式傳入的布林值決定要構造哪一種Sync例項);ReadLock實現了Lock介面、WriteLock也實現了Lock介面。
2、Sync類:
- 1.類的繼承關係
abstract static class Sync extends AbstractQueuedSynchronizer {}
說明:Sync抽象類繼承自AQS抽象類,Sync類提供了對ReentrantReadWriteLock的支援。
- 2.類的內部類
Sync類內部存在兩個內部類,分別為HoldCounter和ThreadLocalHoldCounter,其中HoldCounter主要與讀鎖配套使用。
HoldCounter原始碼如下:
// 計數器
static final class HoldCounter {
// 計數
int count = 0;
// 獲取當前執行緒的TID屬性的值
final long tid = getThreadId(Thread.currentThread());
}
說明:HoldCounter主要有兩個屬性,count和tid,其中count表示某個讀執行緒重入的次數,tid表示該執行緒的tid欄位的值,該欄位可以用來唯一標識一個執行緒。
ThreadLocalHoldCounter的原始碼如下:
// 本地執行緒計數器
static final class ThreadLocalHoldCounter extends ThreadLocal<HoldCounter> {
// 重寫初始化方法,在沒有進行set的情況下,獲取的都是該HoldCounter值
public HoldCounter initialValue() {
return new HoldCounter();
}
}
說明:ThreadLocalHoldCounter重寫了ThreadLocal的initialValue方法,ThreadLocal類可以將執行緒與物件相關聯。在沒有進行set的情況下,get到的均是initialValue方法裡面生成的那個HolderCounter物件。
- 3.類的屬性
abstract static class Sync extends AbstractQueuedSynchronizer {
// 版本序列號
private static final long serialVersionUID = 6317671515068378041L;
// 高16位為讀鎖,低16位為寫鎖 shared_shift
static final int SHARED_SHIFT = 16;
// 讀鎖單位 shared_unit
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
// 讀鎖最大數量 max_count
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
// 寫鎖最大數量 exclusive_mask
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
// 本地執行緒計數器
private transient ThreadLocalHoldCounter readHolds;
// 快取的計數器
private transient HoldCounter cachedHoldCounter;
// 第一個讀執行緒
private transient Thread firstReader = null;
// 第一個讀執行緒的計數
private transient int firstReaderHoldCount;
}
說明:該屬性中包括了讀鎖、寫鎖執行緒的最大量。本地執行緒計數器等。
- 4.類的建構函式
// 建構函式
Sync() {
// 本地執行緒計數器
readHolds = new ThreadLocalHoldCounter();
// 設定AQS的狀態
setState(getState());
}
說明:在Sync的建構函式中設定了本地執行緒計數器和AQS的狀態state。
3、讀寫狀態的設計
同步狀態在重入鎖的實現中是表示被同一個執行緒重複獲取的次數,即一個整形變數來維護,但是之前的那個表示僅僅表示是否鎖定,而不用區分是讀鎖還是寫鎖。而讀寫鎖需要在同步狀態(一個整形變數)上維護多個讀執行緒和一個寫執行緒的狀態。
讀寫鎖對於同步狀態的實現是在一個整形變數上通過“按位切割使用”:將變數切割成兩部分,高16位表示讀,低16位表示寫。
假設當前同步狀態值為S,get和set的操作如下:
- 獲取寫狀態:S&0x0000FFFF:將高16位全部抹去
- 獲取讀狀態:S>>>16:無符號補0,右移16位
- 寫狀態加1:S+1
- 讀狀態加1:S+(1<<16)即S + 0x00010000
在程式碼層的判斷中,如果S不等於0,當寫狀態(S&0x0000FFFF),而讀狀態(S>>>16)大於0,則表示該讀寫鎖的讀鎖已被獲取。
4、寫鎖的獲取與釋放
1.看下WriteLock類中的lock和unlock方法
public void lock() {
sync.acquire(1);
}
public void unlock() {
sync.release(1);
}
可以看到就是呼叫的獨佔式同步狀態的獲取與釋放,因此真實的實現就是Sync的 tryAcquire和 tryRelease。
2.寫鎖的獲取,看下tryAcquire
protected final boolean tryAcquire(int acquires) {
//當前執行緒
Thread current = Thread.currentThread();
//獲取狀態
int c = getState();
//寫執行緒數量(即獲取獨佔鎖的重入數)
int w = exclusiveCount(c);
//當前同步狀態state != 0,說明已經有其他執行緒獲取了讀鎖或寫鎖
if (c != 0) {
// 當前state不為0,此時:如果寫鎖狀態為0說明讀鎖此時被佔用返回false;
// 如果寫鎖狀態不為0且寫鎖沒有被當前執行緒持有返回false
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//判斷同一執行緒獲取寫鎖是否超過最大次數(1<<16-1=65535),支援可重入
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
//更新狀態
//此時當前執行緒已持有寫鎖,現在是重入,所以只需要修改鎖的數量即可。
setState(c + acquires);
return true;
}
//到這裡說明此時c=0,讀鎖和寫鎖都沒有被獲取
//writerShouldBlock表示是否阻塞 非公平鎖(NonfairSync)永遠返回false
if (writerShouldBlock() || !compareAndSetState(c, c + acquires))
return false;
//設定鎖為當前執行緒所有
setExclusiveOwnerThread(current);
return true;
}
其中exclusiveCount方法表示佔有寫鎖的執行緒數量,原始碼如下:
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
說明:直接將狀態state和(2^16 - 1) 做與運算,其等效於將state模上2^16。寫鎖數量由state的低十六位表示。
從原始碼可以看出,獲取寫鎖的步驟如下:
- (1)首先獲取c、w。c表示當前鎖狀態;w表示寫執行緒數量。然後判斷同步狀態state是否為0。如果state!=0,說明已經有其他執行緒獲取了讀鎖或寫鎖,執行(2);否則執行(5)。
- (2)如果鎖狀態不為零(c != 0),而寫鎖的狀態為0(w = 0),說明讀鎖此時被其他執行緒佔用,所以當前執行緒不能獲取寫鎖,自然返回false。或者鎖狀態不為零,而寫鎖的狀態也不為0,但是獲取寫鎖的執行緒不是當前執行緒,則當前執行緒也不能獲取寫鎖。
- (3)判斷當前執行緒獲取寫鎖是否超過最大次數,若超過,拋異常,反之更新同步狀態(此時當前執行緒已獲取寫鎖,更新是執行緒安全的),返回true。
- (4)如果state為0,此時讀鎖或寫鎖都沒有被獲取,判斷是否需要阻塞(公平和非公平方式實現不同),在非公平策略下總是不會被阻塞,在公平策略下會進行判斷(判斷同步佇列中是否有等待時間更長的執行緒,若存在,則需要被阻塞,否則,無需阻塞),如果不需要阻塞,則CAS更新同步狀態,若CAS成功則返回true,失敗則說明鎖被別的執行緒搶去了,返回false。如果需要阻塞則也返回false。
- (5)成功獲取寫鎖後,將當前執行緒設定為佔有寫鎖的執行緒,返回true。
3.方法流程圖如下
4.寫鎖的釋放,tryRelease方法
protected final boolean tryRelease(int releases) {
//若鎖的持有者不是當前執行緒,丟擲異常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//寫鎖的新執行緒數
int nextc = getState() - releases;
//如果獨佔模式重入數為0了,說明獨佔模式被釋放
boolean free = exclusiveCount(nextc) == 0;
if (free)
//若寫鎖的新執行緒數為0,則將鎖的持有者設定為null
setExclusiveOwnerThread(null);
//設定寫鎖的新執行緒數
//不管獨佔模式是否被釋放,更新獨佔重入數
setState(nextc);
return free;
}
寫鎖的釋放過程還是相對而言比較簡單的:首先檢視當前執行緒是否為寫鎖的持有者,如果不是丟擲異常。然後檢查釋放後寫鎖的執行緒數是否為0,如果為0則表示寫鎖空閒了,釋放鎖資源將鎖的持有執行緒設定為null,否則釋放僅僅只是一次重入鎖而已,並不能將寫鎖的執行緒清空。
說明:此方法用於釋放寫鎖資源,首先會判斷該執行緒是否為獨佔執行緒,若不為獨佔執行緒,則丟擲異常,否則,計算釋放資源後的寫鎖的數量,若為0,表示成功釋放,資源不將被佔用,否則,表示資源還被佔用。
5.其方法流程圖如下
5、讀鎖的獲取與釋放
類似於寫鎖,讀鎖的lock和unlock的實際實現對應Sync的 tryAcquireShared 和 tryReleaseShared方法。
1.讀鎖的獲取,看下tryAcquireShared方法
protected final int tryAcquireShared(int unused) {
// 獲取當前執行緒
Thread current = Thread.currentThread();
// 獲取狀態
int c = getState();
//如果寫鎖執行緒數 != 0 ,且獨佔鎖不是當前執行緒則返回失敗,因為存在鎖降級
if (exclusiveCount(c) != 0 && getExclusiveOwnerThread() != current)
return -1;
// 讀鎖數量
int r = sharedCount(c);
/*
* readerShouldBlock():讀鎖是否需要等待(公平鎖原則)
* r < MAX_COUNT:持有執行緒小於最大數(65535)
* compareAndSetState(c, c + SHARED_UNIT):設定讀取鎖狀態
*/
// 讀執行緒是否應該被阻塞、並且小於最大值、並且比較設定成功
if (!readerShouldBlock() && r < MAX_COUNT && compareAndSetState(c, c + SHARED_UNIT)) {
//r == 0,表示第一個讀鎖執行緒,第一個讀鎖firstRead是不會加入到readHolds中
if (r == 0) { // 讀鎖數量為0
// 設定第一個讀執行緒
firstReader = current;
// 讀執行緒佔用的資源數為1
firstReaderHoldCount = 1;
} else if (firstReader == current) { // 當前執行緒為第一個讀執行緒,表示第一個讀鎖執行緒重入
// 佔用資源數加1
firstReaderHoldCount++;
} else { // 讀鎖數量不為0並且不為當前執行緒
// 獲取計數器
HoldCounter rh = cachedHoldCounter;
// 計數器為空或者計數器的tid不為當前正在執行的執行緒的tid
if (rh == null || rh.tid != getThreadId(current))
// 獲取當前執行緒對應的計數器
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0) // 計數為0
//加入到readHolds中
readHolds.set(rh);
//計數+1
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
其中sharedCount方法表示佔有讀鎖的執行緒數量,原始碼如下:
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
說明:直接將state右移16位,就可以得到讀鎖的執行緒數量,因為state的高16位表示讀鎖,對應的第十六位表示寫鎖數量。
讀鎖獲取鎖的過程比寫鎖稍微複雜些,首先判斷寫鎖是否為0並且當前執行緒不佔有獨佔鎖,直接返回;否則,判斷讀執行緒是否需要被阻塞並且讀鎖數量是否小於最大值並且比較設定狀態成功,若當前沒有讀鎖,則設定第一個讀執行緒firstReader和firstReaderHoldCount;若當前執行緒執行緒為第一個讀執行緒,則增加firstReaderHoldCount;否則,將設定當前執行緒對應的HoldCounter物件的值。
fullTryAcquireShared方法:
final int fullTryAcquireShared(Thread current) {
HoldCounter rh = null;
for (;;) { // 無限迴圈
// 獲取狀態
int c = getState();
if (exclusiveCount(c) != 0) { // 寫執行緒數量不為0
if (getExclusiveOwnerThread() != current) // 不為當前執行緒
return -1;
} else if (readerShouldBlock()) { // 寫執行緒數量為0並且讀執行緒被阻塞
// Make sure we're not acquiring read lock reentrantly
if (firstReader == current) { // 當前執行緒為第一個讀執行緒
// assert firstReaderHoldCount > 0;
} else { // 當前執行緒不為第一個讀執行緒
if (rh == null) { // 計數器不為空
rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current)) { // 計數器為空或者計數器的tid不為當前正在執行的執行緒的tid
rh = readHolds.get();
if (rh.count == 0)
readHolds.remove();
}
}
if (rh.count == 0)
return -1;
}
}
if (sharedCount(c
相關推薦
十六、併發程式設計之讀寫鎖認識與原理
一、讀寫鎖認識
排他鎖(寫):在同一時刻只有一個執行緒可以進入
共享鎖(讀):在同一時刻可以有多個執行緒同時進入
package com.roocon.thread.ta4;
import java.util.HashMap;
import java.util.M
十七、併發程式設計之讀寫鎖ReentrantReadWriteLock的鎖降級
鎖降級 鎖降級是指寫鎖降級為讀鎖。 在寫鎖沒有釋放的時候,獲取到讀鎖,再釋放寫鎖
鎖升級(ReentrantReadWriteLock是不支援的。) 把讀鎖升級為寫鎖 在讀鎖沒有釋放的時候,獲取到寫鎖,再釋放讀鎖
oracle官網的對於鎖降級的示例程式碼:
十一、併發程式設計之Lock介面的認識與使用
1、認識
2、優勢
Lock 類似於synchronized,具有相同的互斥性和記憶體可見性,但是更加靈活,加鎖和放鎖可以由使用者自己確定,Synchronized不需要顯示地獲取和釋放鎖,簡單
可以方便的實行公平性
非阻塞的獲取鎖
能被中斷的獲取
十四、併發程式設計之ReentrantLock公平鎖和非公平鎖原理詳解
一、簡介
Java語言中有許多原生執行緒安全的資料結構,比如ArrayBlockingQueue、CopyOnWriteArrayList、LinkedBlockingQueue,它們執行緒安全的實現方式並非通過synchronized關鍵字,而是通過java.util.concurre
十五、併發程式設計之寫自己的公平鎖
package com.roocon.thread.ta3;
import java.util.ArrayList;
import java.util.List;
public class FairLock {
private boolean isLocked = false;
二十七、併發程式設計之併發工具類CountDownLatch詳解
在JDK的併發包裡(java.util.concurrent)提供了這樣幾個非常有用的併發工具類來解決併發程式設計的流程控制。分別是CountDownLatch、CyclicBarrier和Semaphore。
1、CountDownLatch是什麼?
CountDownLatch類
二十五、併發程式設計之join應用與實現原理剖析
1、join有什麼用呢? 當一個執行緒正在進行中的時候,如果我們想呼叫另外一個執行緒的話,這時我們可以使用join。
2、join方法的底層原理,簡單來說就是,join方法能把所呼叫join方法的執行緒進入休眠狀態(wait()),等執行完joinThread執行緒之後,會自動
二十四、併發程式設計之簡易資料連線池
public class MyDataSource {
private LinkedList<Connection> pool = new LinkedList(); //連結串列放連線池用
private static final int INIT_CONNECTION
二十二、併發程式設計之使用Condition實現一個先入先出的有界佇列
//先入先出佇列
public class MyQueue<E> {
private Object[] obj;//陣列(佇列)
private int addIndex;//新增角標
private int removeIndex;//移除腳標
private in
十九、併發程式設計之通過生產者消費者模型理解等待喚醒機制
生產者
//生產者
public class PushTarget implements Runnable{
private Tmail tmail;//銷售平臺
public PushTarget(Tmail tmail) {
this.tmail = tma
十八、併發程式設計之執行緒之間的通訊之wait、notify
一、執行緒的幾種狀態
執行緒有四種狀態:
1.產生(New):執行緒物件已經產生,但尚未被啟動,所以無法執行。如通過new產生了一個執行緒物件後沒對它呼叫start()函式之前。
2.可執行(Runnable):每個支援多執行緒的系統都有一個排程器,排程器會從執行緒池中
十二、併發程式設計之AbstractQueuedSynchronizer(AQS)詳解
一、簡介
1、AQS(java.util.concurrent.locks.AbstractQueuedSynchronizer)是用來構建鎖或者其他同步元件(訊號量、事件等)的基礎框架類。
2、AQS的主要使用方式是繼承它作為一個內部輔助類實現同步原語,它可以簡化你的併發
三十一、併發程式設計之FutureTask詳解
一、Future是一個介面,它定義了5個方法
boolean cancel(boolean mayInterruptInRunning) //取消一個任務,並返回取消結果。引數表示是否中斷執行緒。
boolean isCancelled() //判斷任務是否被取消
Boolean isD
二十九、併發程式設計之併發工具類Semaphore詳解
一、簡介
Semaphore是一個計數訊號量,常用於限制可以訪問某些資源(物理或邏輯的)執行緒數目。 Semaphore是計數訊號量。Semaphore管理一系列許可證。每個acquire方法阻塞,直到有一個許可證可以獲得然後拿走一個許可證;每個release方法增加一個許可證,這可能會
二十八、併發程式設計之併發工具類CyclicBarrier詳解
一、概述
CyclicBarrier是一個同步工具類,它允許一組執行緒互相等待,直到到達某個公共屏障點。與CountDownLatch不同的是該barrier在釋放等待執行緒後可以重用,所以稱它為迴圈(Cyclic)的屏障(Barrier)。 CyclicBarrier支援一個可選的Ru
java多執行緒程式設計之讀寫鎖設計高效能快取器
解決多執行緒執行緒安全問題的主要方法是通過加鎖的方式來實現,當多個執行緒對某個變數進行讀取或寫入的時候通過加鎖來限定只有當前獲取鎖許可權的執行緒才可以對資料進行讀寫,當該執行緒訪問完畢釋放鎖之後其他阻
多執行緒讀寫資料方法之讀寫鎖方法與shared_ptr+互斥鎖方法的比較
對共享資源進行多執行緒讀寫操作有很多方法,本文舉出兩種方法並進行對比。
一:讀寫鎖方法。執行緒進行讀操作時以讀的方式加鎖,執行緒進行寫操作時用寫的方式加鎖。
二:另外一種比較新奇的方法是使用shared_ptr+互斥鎖。shared_ptr是一種用引用計數實現的智慧指標,當
java併發程式設計(六)之讀寫鎖
一、讀寫鎖我們知道在多個執行緒訪問同一個資料的時候是存線上程安全問題的,而在僅僅是讀取資料的時候,是沒有安全問題的,那麼多個執行緒同時讀取資料我們就可以讓其不互斥;而多個執行緒都在修改(寫)資料或有的在讀取有的在寫入的時候再讓其互斥,這樣不但保證執行緒安全而且提高效能。Rea
Python之路(第三十八篇) 併發程式設計:程序同步鎖/互斥鎖、訊號量、事件、佇列、生產者消費者模型
一、程序鎖(同步鎖/互斥鎖)
程序之間資料不共享,但是共享同一套檔案系統,所以訪問同一個檔案,或同一個列印終端,是沒有問題的,
而共享帶來的是競爭,競爭帶來的結果就是錯亂,如何控制,就是加鎖處理。
例子
#併發執行,效率高,但競爭同一列印終端,帶來了列印錯亂
from multiproc
二十、併發程式設計之Condition的使用
三個執行緒按順序迴圈執行
使用執行緒之間的通訊wait、notify寫程式碼
public class Demo {
private int signal;
public synchronized void a() {
while(signal != 0 )