synchronized
synchronized的作用範圍
public class SynchronizedTest {
// 例項方法,方法訪問標誌ACC_SYNCHRONIZED,鎖物件是物件例項
public synchronized void test1(){}
// 靜態方法,方法訪問標誌ACC_SYNCHRONIZED,鎖物件是MetaSpace中的Class
// 相當於類的全域性鎖,會鎖住所有呼叫該方法的執行緒
public synchronized static void test2(){}
public void test3() {
//同步程式碼塊,在程式碼塊前增加monitorenter指令,程式碼塊後增加monitorexit指令
SynchronizedTest synchronizedTest = new SynchronizedTest();
synchronized (synchronizedTest) {}
// 類鎖,效果等同於鎖靜態方法。程式碼塊前後增加monitorenter、monitorexit指令
synchronized (SynchronizedTest.class) {}
}
}
可jclasslib檢視Acc_SYNCHRONIZED標誌和monitorenter、monitorexit指令
test1 方法:
Access flags: 0x0021[public synchronized]
test2 方法:
Access flags: 0x0029[public static synchronized]
test3方法Code操作碼:
0 new #2 <com/java/study/jvm/SynchronizedTest>
3 dup
4 invokespecial #3 <com/java/study/jvm/SynchronizedTest.<init>>
7 astore_1
8 aload_1
9 dup
10 astore_2
11 monitorenter
12 aload_2
13 monitorexit
14 goto 22 (+8)
17 astore_3
18 aload_2
19 monitorexit
20 aload_3
21 athrow
22 ldc #2 <com/java/study/jvm/SynchronizedTest>
24 dup
25 astore_2
26 monitorenter
27 aload_2
28 monitorexit
29 goto 39 (+10)
32 astore 4
34 aload_2
35 monitorexit
36 aload 4
38 athrow
39 return
synchronized實現
核心元件
- Wait Set:哪些呼叫 wait方法被阻塞的執行緒被放置在這裡
- Contention List: 競爭佇列,所有請求鎖的執行緒首先被放在這個競爭佇列中
- Entry List: Contention List 中那些有資格成為候選資源的執行緒被移動到 Entry List 中
- OnDeck:任意時刻, 最多隻有一個執行緒正在競爭鎖資源,該執行緒被成為 OnDeck
- Owner:當前已經獲取到所資源的執行緒被稱為 Owner
- !Owner:當前釋放鎖的執行緒
圖示過程:
解釋:
- JVM 每次從佇列的尾部取出一個資料用於鎖競爭候選者(OnDeck),但是併發情況下,ContentionList 會被大量的併發執行緒進行 CAS 訪問,為了降低對尾部元素的競爭, JVM 會將一部分執行緒移動到 EntryList 中作為候選競爭執行緒。
- Owner 執行緒會在 unlock 時,將 ContentionList 中的部分執行緒遷移到 EntryList 中,並指定EntryList 中的某個執行緒為 OnDeck 執行緒(一般是最先進去的那個執行緒)。
- Owner 執行緒並不直接把鎖傳遞給 OnDeck 執行緒,而是把鎖競爭的權利交給 OnDeck,OnDeck 需要重新競爭鎖。這樣雖然犧牲了一些公平性,但是能極大的提升系統的吞吐量,在JVM 中,也把這種選擇行為稱之為“競爭切換”。
- OnDeck 執行緒獲取到鎖資源後會變為 Owner 執行緒,而沒有得到鎖資源的仍然停留在 EntryList中。如果 Owner 執行緒被 wait 方法阻塞,則轉移到 WaitSet 佇列中,直到某個時刻通過 notify或者 notifyAll 喚醒,會重新進去 EntryList 中。
- 處於 ContentionList、 EntryList、 WaitSet 中的執行緒都處於阻塞狀態,該阻塞是由作業系統來完成的(Linux 核心下采用 pthread_mutex_lock 核心函式實現的)。
- Synchronized 是非公平鎖。 Synchronized 線上程進入 ContentionList 時, 等待的執行緒會先嚐試自旋獲取鎖,如果獲取不到就進入 ContentionList,這明顯對於已經進入佇列的執行緒是不公平的,還有一個不公平的事情就是自旋獲取鎖的執行緒還可能直接搶佔 OnDeck 執行緒的鎖資源。
參考: https://blog.csdn.net/zqz_zqz/article/details/70233767 - 每個物件都有個 monitor 物件, 加鎖就是在競爭 monitor 物件,程式碼塊加鎖是在前後分別加上 monitorenter 和 monitorexit 指令來實現的,方法加鎖是通過一個標記位來判斷的
- synchronized 是一個重量級操作,需要呼叫作業系統相關介面,效能是低效的,有可能給執行緒加鎖消耗的時間比有用操作消耗的時間更多。
- Java1.6, synchronized 進行了很多的優化, 有適應自旋、鎖消除、鎖粗化、輕量級鎖及偏向鎖等,效率有了本質上的提高。在之後推出的 Java1.7 與 1.8 中,均對該關鍵字的實現機理做了優化。引入了偏向鎖和輕量級鎖。都是在物件頭中有標記位,不需要經過作業系統加鎖。
- 鎖可以從偏向鎖升級到輕量級鎖,再升級到重量級鎖。這種升級過程叫做鎖膨脹;
- JDK 1.6 中預設是開啟偏向鎖和輕量級鎖,可以通過-XX:-UseBiasedLocking 來禁用偏向鎖
ReentrantLock
ReentrantLock初始化時,會new一個同步類(預設非公平NonfairSync,當傳入公平引數fair=true時,則new公平類FairSync);而FairSync 和NonfairSync都繼承ReentrantLock中內部類Sync,Sync則繼承同步器AbstractQueuedSynchronizer。UML圖如下(https://www.cnblogs.com/zhimingyang/p/5702752.html 擷取):
Lock流程圖(非公平鎖示例)
原始碼
- ReentrantLock$NonfairSync#lock(),當state為0,即compareAndSetState(0, 1)為true時,獲得鎖;否則進行下一步
- ReentrantLock$NonfairSync#acquire() ——> AbstractQueuedSynchronizer#acquire() --> ReentrantLock$NonfairSync#tryAcquire() -->
ReentrantLock$Sync#nonfairTryAcquire(), 第2次嘗試獲取鎖 - 在上面acquire方法中,還會呼叫addWaiter方法,將一個排他鎖加入佇列
public class ReentrantLock implements Lock, java.io.Serializable {
abstract static class Sync extends AbstractQueuedSynchronizer {
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//第2次嘗試獲取鎖
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
static final class NonfairSync extends Sync {
final void lock() {
// 可不進入佇列,直接搶鎖
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
}
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
public final void acquire(int arg) {
// 步驟3,加入等待佇列,預設排他鎖
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
而繼續addWaiter、enq和acquireQueued則是實現以下圖示過程:
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//前置節點為null的臨界條件,第一個執行緒進入等待佇列
enq(node);
return node;
}
前置節點為null的臨界條件,第一個執行緒進入等待佇列,進行初始化
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//佇列初始化
if (compareAndSetHead(new Node()))
tail = head;
} else {
//雙向連結串列新增元素
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
node屬性值介紹:
對應原始碼:
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
static final class Node {
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() { // Used to establish initial head or SHARED marker
}
Node(Thread thread, Node mode) { // Used by addWaiter
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) { // Used by Condition
this.waitStatus = waitStatus;
this.thread = thread;
}
}
}
重入鎖的實現
重入鎖的可重複進入在以下程式碼中實現(非公平鎖示例,公平鎖程式碼一樣):
- c > 0, 即有鎖,並且獲取鎖的執行緒就是當前執行緒,則將state加1,並更新
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
...
}
// c > 0
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平鎖和非公平鎖
第一處不公平地方(lock方法):
- 非公平鎖lock時,如果發現沒有鎖了,即state為0,可以不管佇列,直接compareAndSetState,如果獲取true了(搶到鎖),直接獲得鎖,不用進同步器中的佇列。
- 而公平鎖沒有此邏輯。
static final class NonfairSync extends Sync {
final void lock() {
// 可不進入佇列,直接搶鎖
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
}
static final class FairSync extends Sync {
final void lock() {
acquire(1);
}
}
第二處不公平的地方(tryAcquire):
- 非公平鎖tryAcquire方法會呼叫Sync#nonfairTryAcquire(),當state為0,發現鎖被釋放時,可直接搶鎖
- 公平鎖則必須滿足!hasQueuedPredecessors()條件,也即必須同步器中佇列沒有執行緒在等待,才去獲取鎖
static final class NonfairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//發現鎖被釋放時,可直接搶鎖
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
...
}
}
公平鎖
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;
}
}
...
}
}
第三處不公平地方,加入佇列時,前置節點是頭節點:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
...
}
}
}