警告⚠️:本文耗時很長,先做好心理准備,建議PC端瀏覽器瀏覽效果更佳。
-
當沒有競爭出現時,默認會使用偏向鎖。JVM 會利用 CAS 操作(compare and swap), 在對象頭上的 Mark Word 部分設置線程 ID,以表示這個對象偏向於當前線程,所以並不 涉及真正的互斥鎖。這樣做的假設是基於在很多應用場景中,大部分對象生命周期中最多會 被一個線程鎖定,使用偏 向 鎖可以降低無競爭開銷。
-
如果有另外的線程試圖鎖定某個已經被偏 向 過的對象,JVM 就需要撤銷(revoke)偏 向 鎖,並切換到輕量級鎖實現。輕量級鎖依賴 CAS 操作 Mark Word 來試圖獲取鎖,如果重 試成功,就使用輕量級鎖;否則,進一步升級為重量級鎖
public class TestDemo {
}
public class DemoExample1 {
static TestDemo testDemo;
public static void main(String[] args) throws Exception {
testDemo= new TestDemo();
synchronized (testDemo){
System.out.println("lock ing");
testDemo.hashCode();
System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
}
}
}
javap -c DemoExample1.class
- 當執行 monitorenter 時,如果目標鎖對象的計數器為 0,那么說明它沒有被其他線程所持有。在這個情況下,Java 虛擬機會將該鎖對象的持有線程設置為當前線程,並且將其計數器加 1。
- 在目標鎖對象的計數器不為 0 的情況下,如果鎖對象的持有線程是當前線程,那么 Java 虛擬機可以將其計數器加 1,否則需要等待,直至持有線程釋放該鎖。當執行 monitorexit 時,Java 虛擬機則需將鎖對象的計數器減 1。當計數器減為 0 時,那便代表該鎖已經被釋放掉了。
- 之所以采用這種計數器的方式,是為了允許同一個線程重復獲取同一把鎖。舉個例子,如果一個 Java 類中擁有多個 synchronized 方法,那么這些方法之間的相互調用,不管是直接的還是間接的,都會涉及對同一把鎖的重復加鎖操作。因此,我們需要設計這么一個可重入的特性,來避免編程里的隱式約束。
public class DemoExample3 {
public int sharedState;
public void nonSafeAction() {
while (sharedState < 100000) {
int former = sharedState++;
int latter = sharedState;
if (former != latter - 1) {
System.out.println("Observed data race, former is " +
former + ", " + "latter is " + latter);
}
}
}
public static void main(String[] args) throws InterruptedException {
final DemoExample3 demoExample3 = new DemoExample3();
Thread thread1 = new Thread() {
@Override
public void run() {
demoExample3.nonSafeAction();
}
};
Thread thread2 = new Thread() {
@Override
public void run() {
demoExample3.nonSafeAction();
}
};
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
public class DemoExample3 {
public int sharedState;
public void nonSafeAction() {
while (sharedState < 100000) {
synchronized (this) {
int former = sharedState++;
int latter = sharedState;
if (former != latter - 1) {
System.out.println("Observed data race, former is " +
former + ", " + "latter is " + latter);
}
}
}
}
public static void main(String[] args) throws InterruptedException {
final DemoExample3 demoExample3 = new DemoExample3();
Thread thread1 = new Thread() {
@Override
public void run() {
demoExample3.nonSafeAction();
}
};
Thread thread2 = new Thread() {
@Override
public void run() {
demoExample3.nonSafeAction();
}
};
thread1.start();
thread2.start();
thread1.join();
thread2.join();
}
}
這次看下加上synchronized關鍵字的打印出來的結果:
// Handles the uncommon case in locking, i.e., contention or an inflated lock.
JRT_BLOCK_ENTRY(void, SharedRuntime::complete_monitor_locking_C(oopDesc* _obj, BasicLock* lock, JavaThread* thread))
// Disable ObjectSynchronizer::quick_enter() in default config
// on AARCH64 and ARM until JDK-8153107 is resolved.
if (ARM_ONLY((SyncFlags & 256) != 0 &&)
AARCH64_ONLY((SyncFlags & 256) != 0 &&)
!SafepointSynchronize::is_synchronizing()) {
// Only try quick_enter() if we're not trying to reach a safepoint
// so that the calling thread reaches the safepoint more quickly.
if (ObjectSynchronizer::quick_enter(_obj, thread, lock)) return;
}
// NO_ASYNC required because an async exception on the state transition destructor
// would leave you with the lock held and it would never be released.
// The normal monitorenter NullPointerException is thrown without acquiring a lock
// and the model is that an exception implies the method failed.
JRT_BLOCK_NO_ASYNC
oop obj(_obj);
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(THREAD, obj);
//在 JVM 啟動時,我們可以指定是否開啟偏向鎖
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
//fast_enter 是我們熟悉的完整鎖獲取路徑
ObjectSynchronizer::fast_enter(h_obj, lock, true, CHECK);
} else {
//slow_enter 則是繞過偏向鎖,直接進入輕量級鎖獲取邏輯
ObjectSynchronizer::slow_enter(h_obj, lock, CHECK);
}
assert(!HAS_PENDING_EXCEPTION, "Should have no exception here");
JRT_BLOCK_END
JRT_END
// -----------------------------------------------------------------------------
// Fast Monitor Enter/Exit
// This the fast monitor enter. The interpreter and compiler use
// some assembly copies of this code. Make sure update those code
// if the following function is changed. The implementation is
// extremely sensitive to race condition. Be careful.
void ObjectSynchronizer::fast_enter(Handle obj, BasicLock* lock,
bool attempt_rebias, TRAPS) {
if (UseBiasedLocking) {
if (!SafepointSynchronize::is_at_safepoint()) {
//biasedLocking定義了偏向鎖相關操作,revoke_and_rebias revokeatsafepoint 則定義了當檢測到安全點時的處理
BiasedLocking::Condition cond = BiasedLocking::revoke_and_rebias(obj, attempt_rebias, THREAD);
if (cond == BiasedLocking::BIAS_REVOKED_AND_REBIASED) {
return;
}
} else {
assert(!attempt_rebias, "can not rebias toward VM thread");
BiasedLocking::revoke_at_safepoint(obj);
}
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
//如果獲取偏向鎖失敗,則進入 slow_enter,鎖升級
slow_enter(obj, lock, THREAD);
}
// -----------------------------------------------------------------------------
// Interpreter/Compiler Slow Case
// This routine is used to handle interpreter/compiler slow case
// We don't need to use fast path here, because it must have been
// failed in the interpreter/compiler code.
void ObjectSynchronizer::slow_enter(Handle obj, BasicLock* lock, TRAPS) {
markOop mark = obj->mark();
assert(!mark->has_bias_pattern(), "should not see bias pattern here");
if (mark->is_neutral()) {
// Anticipate successful CAS -- the ST of the displaced mark must
// be visible <= the ST performed by the CAS.
// 將目前的 Mark Word 復制到 Displaced Header 上
lock->set_displaced_header(mark);
// 利用 CAS 設置對象的 Mark Wo
if (mark == obj()->cas_set_mark((markOop) lock, mark)) {
return;
}
// Fall through to inflate() …
// 檢查存在競爭
} else if (mark->has_locker() &&
THREAD->is_lock_owned((address)mark->locker())) {
assert(lock != mark->locker(), "must not re-lock the same lock");
assert(lock != (BasicLock*)obj->mark(), "don't relock with same BasicLock”);
// 清除
lock->set_displaced_header(NULL);
return;
}
// The object header will never be displaced to this lock,
// so it does not matter what the value is, except that it
// must be non-zero to avoid looking like a re-entrant lock,
// and must not look locked either.
// 重置 Displaced Header
lock->set_displaced_header(markOopDesc::unused_mark());
//鎖膨脹
ObjectSynchronizer::inflate(THREAD,
obj(),
inflate_cause_monitor_enter)->enter(THREAD);
}
// This routine is used to handle interpreter/compiler slow case
// We don't need to use fast path here, because it must have
// failed in the interpreter/compiler code. Simply use the heavy
// weight monitor should be ok, unless someone find otherwise.
void ObjectSynchronizer::slow_exit(oop object, BasicLock* lock, TRAPS) {
fast_exit(object, lock, THREAD);
}
//鎖膨脹
ObjectMonitor * ATTR ObjectSynchronizer::inflate (Thread * Self, oop object) {
// Inflate mutates the heap ...
// Relaxing assertion for bug 6320749.
assert (Universe::verify_in_progress() ||
!SafepointSynchronize::is_at_safepoint(), "invariant") ;
for (;;) {//自旋
const markOop mark = object->mark() ;
assert (!mark->has_bias_pattern(), "invariant") ;
// The mark can be in one of the following states:
// * Inflated - just return
// * Stack-locked - coerce it to inflated
// * INFLATING - busy wait for conversion to complete
// * Neutral - aggressively inflate the object.
// * BIASED - Illegal. We should never see this
// CASE: inflated已膨脹,即重量級鎖
if (mark->has_monitor()) {//判斷當前是否為重量級鎖
ObjectMonitor * inf = mark->monitor() ;//獲取指向ObjectMonitor的指針
assert (inf->header()->is_neutral(), "invariant");
assert (inf->object() == object, "invariant") ;
assert (ObjectSynchronizer::verify_objmon_isinpool(inf), "monitor is invalid");
return inf ;
}
// CASE: inflation in progress - inflating over a stack-lock.膨脹等待(其他線程正在從輕量級鎖轉為膨脹鎖)
// Some other thread is converting from stack-locked to inflated.
// Only that thread can complete inflation -- other threads must wait.
// The INFLATING value is transient.
// Currently, we spin/yield/park and poll the markword, waiting for inflation to finish.
// We could always eliminate polling by parking the thread on some auxiliary list.
if (mark == markOopDesc::INFLATING()) {
TEVENT (Inflate: spin while INFLATING) ;
ReadStableMark(object) ;
continue ;
}
// CASE: stack-locked棧鎖(輕量級鎖)
// Could be stack-locked either by this thread or by some other thread.
//
// Note that we allocate the objectmonitor speculatively, _before_ attempting
// to install INFLATING into the mark word. We originally installed INFLATING,
// allocated the objectmonitor, and then finally STed the address of the
// objectmonitor into the mark. This was correct, but artificially lengthened
// the interval in which INFLATED appeared in the mark, thus increasing
// the odds of inflation contention.
//
// We now use per-thread private objectmonitor free lists.
// These list are reprovisioned from the global free list outside the
// critical INFLATING...ST interval. A thread can transfer
// multiple objectmonitors en-mass from the global free list to its local free list.
// This reduces coherency traffic and lock contention on the global free list.
// Using such local free lists, it doesn't matter if the omAlloc() call appears
// before or after the CAS(INFLATING) operation.
// See the comments in omAlloc().
if (mark->has_locker()) {
ObjectMonitor * m = omAlloc (Self) ;//獲取一個可用的ObjectMonitor
// Optimistically prepare the objectmonitor - anticipate successful CAS
// We do this before the CAS in order to minimize the length of time
// in which INFLATING appears in the mark.
m->Recycle();
m->_Responsible = NULL ;
m->OwnerIsThread = 0 ;
m->_recursions = 0 ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // Consider: maintain by type/class
markOop cmp = (markOop) Atomic::cmpxchg_ptr (markOopDesc::INFLATING(), object->mark_addr(), mark) ;
if (cmp != mark) {//CAS失敗//CAS失敗,說明沖突了,自旋等待//CAS失敗,說明沖突了,自旋等待//CAS失敗,說明沖突了,自旋等待
omRelease (Self, m, true) ;//釋放監視器鎖
continue ; // Interference -- just retry
}
// We've successfully installed INFLATING (0) into the mark-word.
// This is the only case where 0 will appear in a mark-work.
// Only the singular thread that successfully swings the mark-word
// to 0 can perform (or more precisely, complete) inflation.
//
// Why do we CAS a 0 into the mark-word instead of just CASing the
// mark-word from the stack-locked value directly to the new inflated state?
// Consider what happens when a thread unlocks a stack-locked object.
// It attempts to use CAS to swing the displaced header value from the
// on-stack basiclock back into the object header. Recall also that the
// header value (hashcode, etc) can reside in (a) the object header, or
// (b) a displaced header associated with the stack-lock, or (c) a displaced
// header in an objectMonitor. The inflate() routine must copy the header
// value from the basiclock on the owner's stack to the objectMonitor, all
// the while preserving the hashCode stability invariants. If the owner
// decides to release the lock while the value is 0, the unlock will fail
// and control will eventually pass from slow_exit() to inflate. The owner
// will then spin, waiting for the 0 value to disappear. Put another way,
// the 0 causes the owner to stall if the owner happens to try to
// drop the lock (restoring the header from the basiclock to the object)
// while inflation is in-progress. This protocol avoids races that might
// would otherwise permit hashCode values to change or "flicker" for an object.
// Critically, while object->mark is 0 mark->displaced_mark_helper() is stable.
// 0 serves as a "BUSY" inflate-in-progress indicator
// fetch the displaced mark from the owner's stack.
// The owner can't die or unwind past the lock while our INFLATING
// object is in the mark. Furthermore the owner can't complete
// an unlock on the object, either.
markOop dmw = mark->displaced_mark_helper() ;
assert (dmw->is_neutral(), "invariant") ;
//CAS成功,設置ObjectMonitor的_header、_owner和_object等
// Setup monitor fields to proper values -- prepare the monitor
m->set_header(dmw) ;
// Optimization: if the mark->locker stack address is associated
// with this thread we could simply set m->_owner = Self and
// m->OwnerIsThread = 1. Note that a thread can inflate an object
// that it has stack-locked -- as might happen in wait() -- directly
// with CAS. That is, we can avoid the xchg-NULL .... ST idiom.
m->set_owner(mark->locker());
m->set_object(object);
// TODO-FIXME: assert BasicLock->dhw != 0.
// Must preserve store ordering. The monitor state must
// be stable at the time of publishing the monitor address.
guarantee (object->mark() == markOopDesc::INFLATING(), "invariant") ;
object->release_set_mark(markOopDesc::encode(m));
// Hopefully the performance counters are allocated on distinct cache lines
// to avoid false sharing on MP systems ...
if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
TEVENT(Inflate: overwrite stacklock) ;
if (TraceMonitorInflation) {
if (object->is_instance()) {
ResourceMark rm;
tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(void *) object, (intptr_t) object->mark(),
object->klass()->external_name());
}
}
return m ;
}
// CASE: neutral 無鎖
// TODO-FIXME: for entry we currently inflate and then try to CAS _owner.
// If we know we're inflating for entry it's better to inflate by swinging a
// pre-locked objectMonitor pointer into the object header. A successful
// CAS inflates the object *and* confers ownership to the inflating thread.
// In the current implementation we use a 2-step mechanism where we CAS()
// to inflate and then CAS() again to try to swing _owner from NULL to Self.
// An inflateTry() method that we could call from fast_enter() and slow_enter()
// would be useful.
assert (mark->is_neutral(), "invariant");
ObjectMonitor * m = omAlloc (Self) ;
// prepare m for installation - set monitor to initial state
m->Recycle();
m->set_header(mark);
m->set_owner(NULL);
m->set_object(object);
m->OwnerIsThread = 1 ;
m->_recursions = 0 ;
m->_Responsible = NULL ;
m->_SpinDuration = ObjectMonitor::Knob_SpinLimit ; // consider: keep metastats by type/class
if (Atomic::cmpxchg_ptr (markOopDesc::encode(m), object->mark_addr(), mark) != mark) {
m->set_object (NULL) ;
m->set_owner (NULL) ;
m->OwnerIsThread = 0 ;
m->Recycle() ;
omRelease (Self, m, true) ;
m = NULL ;
continue ;
// interference - the markword changed - just retry.
// The state-transitions are one-way, so there's no chance of
// live-lock -- "Inflated" is an absorbing state.
}
// Hopefully the performance counters are allocated on distinct
// cache lines to avoid false sharing on MP systems ...
if (ObjectMonitor::_sync_Inflations != NULL) ObjectMonitor::_sync_Inflations->inc() ;
TEVENT(Inflate: overwrite neutral) ;
if (TraceMonitorInflation) {
if (object->is_instance()) {
ResourceMark rm;
tty->print_cr("Inflating object " INTPTR_FORMAT " , mark " INTPTR_FORMAT " , type %s",
(void *) object, (intptr_t) object->mark(),
object->klass()->external_name());
}
}
return m ;
}
}
1、整個膨脹過程在自旋下完成;
2、mark->has_monitor()方法判斷當前是否為重量級鎖,即Mark Word的鎖標識位為 10,如果當前狀態為重量級鎖,執行步驟(3),否則執行步驟(4);
3、mark->monitor()方法獲取指向ObjectMonitor的指針,並返回,說明膨脹過程已經完成;
4、如果當前鎖處於膨脹中,說明該鎖正在被其它線程執行膨脹操作,則當前線程就進行自旋等待鎖膨脹完成,這里需要注意一點,雖然是自旋操作,但不會一直占用cpu資源,每隔一段時間會通過os::NakedYield方法放棄cpu資源,或通過park方法掛起;如果其他線程完成鎖的膨脹操作,則退出自旋並返回;
5、如果當前是輕量級鎖狀態,即鎖標識位為 00,膨脹過程如下:
- 通過omAlloc方法,獲取一個可用的ObjectMonitor monitor,並重置monitor數據;
- 通過CAS嘗試將Mark Word設置為markOopDesc:INFLATING,標識當前鎖正在膨脹中,如果CAS失敗,說明同一時刻其它線程已經將Mark Word設置為markOopDesc:INFLATING,當前線程進行自旋等待膨脹完成;
- 如果CAS成功,設置monitor的各個字段:_header、_owner和_object等,並返回;
6、如果是無鎖,重置監視器值;
-
偏向鎖是指一段同步代碼一直被一個線程所訪問,那么該線程會自動獲取鎖,降低獲取鎖的代價。
-
在大多數情況下,鎖總是由同一線程多次獲得,不存在多線程競爭,所以出現了偏向鎖。其目標就是在只有一個線程執行同步代碼塊時能夠提高性能。
-
當一個線程訪問同步代碼塊並獲取鎖時,會在Mark Word里存儲鎖偏向的線程ID。在線程進入和退出同步塊時不再通過CAS操作來加鎖和解鎖,而是檢測Mark Word里是否存儲着指向當前線程的偏向鎖。引入偏向鎖是為了在無多線程競爭的情況下盡量減少不必要的輕量級鎖執行路徑,因為輕量級鎖的獲取及釋放依賴多次CAS原子指令,而偏向鎖只需要在置換ThreadID的時候依賴一次CAS原子指令即可。
-
偏向鎖只有遇到其他線程嘗試競爭偏向鎖時,持有偏向鎖的線程才會釋放鎖,線程不會主動釋放偏向鎖。偏向鎖的撤銷,需要等待全局安全點(在這個時間點上沒有字節碼正在執行),它會首先暫停擁有偏向鎖的線程,判斷鎖對象是否處於被鎖定狀態。撤銷偏向鎖后恢復到無鎖(標志位為“01”)或輕量級鎖(標志位為“00”)的狀態。
-
偏向鎖在JDK 6及以后的JVM里是默認啟用的。可以通過JVM參數關閉偏向鎖:-XX:-UseBiasedLocking=false,關閉之后程序默認會進入輕量級鎖狀態。
//創建一個啥都沒有的類:
public class TestDemo {}
public class DemoExample {
static TestDemo testDemo;
public static void main(String[] args) throws Exception {
//此處睡眠50000ms,取消jvm默認偏向鎖延遲4000ms
Thread.sleep(5000);
testDemo= new TestDemo();
//hash計算?
//testDemo.hashCode();
System.out.println("befor lock");
//無鎖:偏向鎖?
System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
synchronized (testDemo){
System.out.println("lock ing");
System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
}
System.out.println("after lock");
System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
}
}
-
具體來說,在線程進行加鎖時,如果該鎖對象支持偏向鎖,那么 Java 虛擬機會通過 CAS 操作,將當前線程的地址記錄在鎖對象的標記字段之中,並且將標記字段的最后三位設置為: 1 01;
-
在接下來的運行過程中,每當有線程請求這把鎖,Java 虛擬機只需判斷鎖對象標記字段 中:最后三位是否為: 1 01,是否包含當前線程的地址,以及 epoch 值是否和鎖對象的類的 epoch 值相同。如果都滿足,那么當前線程持有該偏向鎖,可以直接返回;
-
我們先從偏向鎖的撤銷講起。當請求加鎖的線程和鎖對象標記字段保持的線程地址不匹配時 (而且 epoch 值相等,如若不等,那么當前線程可以將該鎖重偏向至自己),Java 虛擬機 需要撤銷該偏向鎖。這個撤銷過程非常麻煩,它要求持有偏向鎖的線程到達安全點,再將偏 向鎖替換成輕量級鎖;
-
如果某一類鎖對象的總撤銷數超過了一個閾值(對應 jvm參數 - XX:BiasedLockingBulkRebiasThreshold,默認為 20),那么 Java 虛擬機會宣布這個類 的偏向鎖失效;(這里說的就是 批量重偏向 )
product(intx, BiasedLockingBulkRebiasThreshold, 20, \
"Threshold of number of revocations per type to try to " \
"rebias all objects in the heap of that type") \
range(0, max_intx) \
constraint(BiasedLockingBulkRebiasThresholdFunc,AfterErgo) \
-
具體的做法便是在每個類中維護一個 epoch 值,你可以理解為第幾代偏向鎖。當設置偏向 鎖時,Java 虛擬機需要將該 epoch 值復制到鎖對象的標記字段中;
-
在宣布某個類的偏向鎖失效時,Java 虛擬機實則將該類的 epoch 值加 1,表示之前那一代 的偏向鎖已經失效。而新設置的偏向鎖則需要復制新的 epoch 值;
-
為了保證當前持有偏向鎖並且已加鎖的線程不至於因此丟鎖,Java 虛擬機需要遍歷所有線 程的 Java 棧,找出該類已加鎖的實例,並且將它們標記字段中的 epoch 值加 1。該操作 需要所有線程處於安全點狀態;
-
如果總撤銷數超過另一個閾值(對應 jvm 參數 - XX:BiasedLockingBulkRevokeThreshold,默認值為 40),那么 Java 虛擬機會認為這個 類已經不再適合偏向鎖。此時,Java 虛擬機會撤銷該類實例的偏向鎖,並且在之后的加鎖 過程中直接為該類實例設置輕量級鎖(這里說的就是 偏向批量撤銷 )
product(intx, BiasedLockingBulkRevokeThreshold, 40, \
"Threshold of number of revocations per type to permanently " \
"revoke biases of all objects in the heap of that type") \
range(0, max_intx) \
constraint(BiasedLockingBulkRevokeThresholdFunc,AfterErgo)
public class TestDemo {
}
public class DemoExample4 {
public static void main(String[] args) throws InterruptedException {
test1();
}
public class DemoExample5 {
public static void main(String[] args) throws InterruptedException {
test1();
}
/**
* 僅證明批量重偏向
* @throws InterruptedException
*/
public static void test1() throws InterruptedException {
List<TestDemo> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add(new TestDemo());
}
Thread t1 = new Thread(()->{
System.out.println("加鎖前 get(0) 應該是無鎖可偏向 "+ ClassLayout.parseInstance(list.get(0)).toPrintable());
for (TestDemo a:list ) {
synchronized (a){
System.out.print("加鎖 >");
}
}
System.out.println();
System.out.println("加鎖后 get(0) 應該是偏向鎖"+ClassLayout.parseInstance(list.get(0)).toPrintable());
try {
TimeUnit.SECONDS.sleep(1000);//這里不讓線程死,防止線程ID復用
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
TimeUnit.SECONDS.sleep(5);
Thread t2 = new Thread(()->{
for (int i = 0; i < 40; i++) {
TestDemo a = list.get(i);
synchronized (a){
System.out.print("加鎖 >");
}
if (i==18){
System.out.println();
System.out.println("加鎖后 get(18) 應該是無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(i)).toPrintable());
}
if (i==19){ //開始重偏向
System.out.println();
System.out.println("加鎖后 get(19) 應該是偏向鎖 "+ClassLayout.parseInstance(list.get(i)).toPrintable());
System.out.println("加鎖后 get(0) 應該是無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(0)).toPrintable());
System.out.println("加鎖后 get(99) 應該是偏向鎖 偏向t1 "+ClassLayout.parseInstance(list.get(99)).toPrintable());
}
if (i==20){
System.out.println();
System.out.println("加鎖后 get(20) 應該是偏向鎖 "+ClassLayout.parseInstance(list.get(i)).toPrintable());
}
}
});
t2.start();
}
}
public class TestDemo {
}
public class DemoExample7 {
public static void main(String[] args) throws Exception {
List<TestDemo> list = new ArrayList<>();
//初始化數據
for (int i = 0; i < 100; i++) {
list.add(new TestDemo());
}
Thread t1 = new Thread() {
String name = "1";
public void run() {
System.out.printf(name);
for (TestDemo a : list) {
synchronized (a) {
if (a == list.get(10)) {
System.out.println("t1 預期是偏向鎖" + 10 + ClassLayout.parseInstance(a).toPrintable());
}
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(5000);
System.out.println("main 預期是偏向鎖" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());
Thread t2 = new Thread() {
String name = "2";
public void run() {
System.out.printf(name);
for (int i = 0; i < 100; i++) {
TestDemo a = list.get(i);
// hack 為了在批量重偏向發生后再次加鎖,前面使用了輕量級鎖的對象
if (i == 20) {
a = list.get(9);
}
synchronized (a) {
if (i == 10) {
//已經經過偏向鎖撤銷,並使用輕量級鎖的對象,釋放后 狀態依為001 無鎖狀態
System.out.println("t2 i=10 get(1)預期是無鎖" + ClassLayout.parseInstance(list.get(1)).toPrintable());
//因為和t1交替使用對象a 沒有發生競爭,但偏向鎖已偏向,另外不滿足重偏向條件,所以使用輕量級鎖
System.out.println("t2 i=10 get(i) 預期輕量級鎖 " + i + ClassLayout.parseInstance(a).toPrintable());
}
if (i == 19) {
//已經經過偏向鎖撤銷,並使用輕量級鎖的對象,在批量重偏向發生后。不會影響現有的狀態 狀態依然為001
System.out.println("t2 i=19 get(10)預期是無鎖" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());
//滿足重偏向條件后,已偏向的對象可以重新使用偏向鎖 將線程id指向當前線程,101
System.out.println("t2 i=19 get(i) 滿足重偏向條件20 預期偏向鎖 " + i + ClassLayout.parseInstance(a).toPrintable());
//滿足重偏向條件后,已偏向還為需要加鎖的對象依然偏向線程1 因為偏向鎖的撤銷是發生在下次加鎖的時候。這里沒有執行到同步此對象,所以依然偏向t1
System.out.println("t2 i=19 get(i) 滿足重偏向條件20 但后面的對象沒有被加鎖,所以依舊偏向t1 " + i + ClassLayout.parseInstance(list.get(40)).toPrintable());
}
if (i == 20) {
//滿足重偏向條件后,再次加鎖之前使用了輕量級鎖的對象,依然輕量級鎖,證明重偏向這個狀態只針對偏向鎖。已經發生鎖升級的,不會退回到偏向鎖
System.out.println("t2 i=20 滿足偏向條件之后,之前被設置為無鎖狀態的對象,不可偏向,這里使用的是輕量級鎖 get(9)預期是輕量級鎖 " + ClassLayout.parseInstance(a).toPrintable());
}
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t2.start();
Thread.sleep(5000);
}
}
public class TestDemo {
}
public class DemoExample6 {
public static void main(String[] args) throws InterruptedException {
test2();
}
/**
* 證明偏量偏向撤銷
* @throws InterruptedException
*/
public static void test2() throws InterruptedException {
List<TestDemo> list = new ArrayList<TestDemo>();
for (int i = 0; i < 100; i++) {
list.add(new TestDemo());
}
Thread t1 = new Thread(()->{
System.out.println("加鎖前 get(0) 應該是無鎖可偏向 "+ClassLayout.parseInstance(list.get(0)).toPrintable());
for (TestDemo a:list ) {
synchronized (a){
System.out.print("加鎖 >");
}
}
System.out.println();
System.out.println("加鎖后 get(0) 應該是偏向鎖"+ClassLayout.parseInstance(list.get(0)).toPrintable());
try {
TimeUnit.SECONDS.sleep(1000);//這里不讓線程死,防止線程ID復用
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
TimeUnit.SECONDS.sleep(5);
Thread t2 = new Thread(()->{
for (int i = 0; i < 100; i++) {
TestDemo a = list.get(i);
synchronized (a){
System.out.println(Thread.currentThread().getId()+"加鎖 >");
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i==9){//這里剛好是第19個上鎖的(同樣是第19個偏向鎖升級的)
System.out.println();
System.out.println("加鎖后 get(9) 應該是無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(i)).toPrintable());
}
if (i==10){//這里剛好是第21個上鎖的
System.out.println();
System.out.println("加鎖后 get(10) 應該是偏向鎖 偏向t2 "+ClassLayout.parseInstance(list.get(i)).toPrintable());
}
if (i==50){//50開始升級為輕量級鎖(同樣是第21個偏向鎖升級的)
System.out.println();
System.out.println("加鎖后 get(50) 無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(i)).toPrintable());
}
if (i==59){//60(同樣是第39個偏向鎖升級的)
System.out.println();
System.out.println("加鎖后 get(59) 無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(i)).toPrintable());
}
if (i==69){//69(同樣是第59個偏向鎖升級的)
System.out.println();
System.out.println("加鎖后 get(69) 無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(i)).toPrintable());
TestDemo a1 = new TestDemo();
synchronized (a1){
System.out.println("偏向撤銷發生后的該類新建的對象都不會再偏向任何線程 "+ClassLayout.parseInstance(a1).toPrintable());
}
}
}
});
Thread t3 = new Thread(()->{
for (int i = 99; i >= 0; i--) {
TestDemo a = list.get(i);
synchronized (a){
System.out.println(Thread.currentThread().getId()+"加鎖 >");
}
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**
* 重點:重偏向撤銷
*/
if (i==40){//40升級為輕量級鎖(同樣是第40個偏向鎖升級的,這時候發生偏向撤銷)
System.out.println();
System.out.println("加鎖后 get("+i+") 應該是無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(0)).toPrintable());
TestDemo a1 = new TestDemo();
synchronized (a1){
System.out.println("偏向撤銷發生后的該類新建的對象都不會再偏向任何線程 "+ClassLayout.parseInstance(a1).toPrintable());
}
}
if (i==30){//39升級為輕量級鎖(同樣是第42個偏向鎖升級的)
System.out.println();
System.out.println("加鎖后 get("+i+") 應該是無鎖(輕量級鎖釋放) "+ClassLayout.parseInstance(list.get(0)).toPrintable());
TestDemo a1 = new TestDemo();
synchronized (a1){
System.out.println("偏向撤銷發生后的該類新建的對象都不會再偏向任何線程 "+ClassLayout.parseInstance(a1).toPrintable());
}
}
}
});
t2.start();
TimeUnit.MILLISECONDS.sleep(50);
t3.start();
}
}
public class TestDemo {
}
public class DemoExample8 {
public static void main(String[] args) throws Exception {
List<TestDemo> list = new ArrayList<>();
List<TestDemo> list2 = new ArrayList<>();
List<TestDemo> list3 = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add(new TestDemo());
list2.add(new TestDemo());
list3.add(new TestDemo());
}
//偏向鎖
System.out.println("初始狀態" + 10 + ClassLayout.parseClass(TestDemo.class).toPrintable());
Thread t1 = new Thread() {
String name = "1";
public void run() {
System.out.printf(name);
for (TestDemo a : list) {
synchronized (a) {
if (a == list.get(10)) {
//偏向鎖
System.out.println("t1 預期是偏向鎖" + 10 + ClassLayout.parseInstance(a).toPrintable());
}
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.start();
Thread.sleep(5000);
//偏向鎖
System.out.println("main 預期是偏向鎖" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());
Thread t2 = new Thread() {
String name = "2";
public void run() {
System.out.printf(name);
for (int i = 0; i < 100; i++) {
TestDemo a = list.get(i);
synchronized (a) {
if (a == list.get(10)) {
System.out.println("t2 i=10 get(1)預期是無鎖" + ClassLayout.parseInstance(list.get(1)).toPrintable());//偏向鎖
System.out.println("t2 i=10 get(10) 預期輕量級鎖 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖
}
if (a == list.get(19)) {
System.out.println("t2 i=19 get(10)預期是無鎖" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//偏向鎖
System.out.println("t2 i=19 get(19) 滿足重偏向條件20 預期偏向鎖 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖
System.out.println("類的對象累計撤銷達到20");
}
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t2.start();
Thread.sleep(5000);
Thread t3 = new Thread() {
String name = "3";
public void run() {
System.out.printf(name);
for (TestDemo a : list2) {
synchronized (a) {
if (a == list2.get(10)) {
System.out.println("t3 預期是偏向鎖" + 10 + ClassLayout.parseInstance(a).toPrintable());//偏向鎖
}
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t3.start();
Thread.sleep(5000);
Thread t4 = new Thread() {
String name = "4";
public void run() {
System.out.printf(name);
for (int i = 0; i < 100; i++) {
TestDemo a = list2.get(i);
synchronized (a) {
if (a == list2.get(10)) {
System.out.println("t4 i=10 get(1)預期是無鎖" + ClassLayout.parseInstance(list2.get(1)).toPrintable());//偏向鎖
System.out.println("t4 i=10 get(10) 當前不滿足重偏向條件 20 預期輕量級鎖 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖
}
if (a == list2.get(19)) {
System.out.println("t4 i=19 get(10)預期是無鎖" + 10 + ClassLayout.parseInstance(list2.get(10)).toPrintable());//偏向鎖
System.out.println("t4 i=19 get(19) 當前滿足重偏向條件 20 但A類的對象累計撤銷達到40 預期輕量級鎖 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖
System.out.println("類的對象累計撤銷達到40");
}
if (a == list2.get(20)) {
System.out.println("t4 i=20 get(20) 當前滿足重偏向條件 20 預期輕量級鎖 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖
}
}
}
}
};
t4.start();
Thread.sleep(5000);
System.out.println("main 預期是偏向鎖" + 10 + ClassLayout.parseInstance(list3.get(0)).toPrintable());//偏向鎖
Thread t5 = new Thread() {
String name = "5";
public void run() {
System.out.printf(name);
for (TestDemo a : list3) {
synchronized (a) {
if (a == list3.get(10)) {
System.out.println("t5 預期是輕量級鎖,類的對象累計撤銷達到40 不可以用偏向鎖了" + 10 + ClassLayout.parseInstance(a).toPrintable());//偏向鎖
}
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t5.start();
Thread.sleep(5000);
System.out.println("main 預期是偏向鎖" + 10 + ClassLayout.parseInstance(list.get(10)).toPrintable());//偏向鎖
Thread t6 = new Thread() {
String name = "6";
public void run() {
System.out.printf(name);
for (int i = 0; i < 100; i++) {
TestDemo a = list3.get(i);
synchronized (a) {
if (a == list3.get(10)) {
System.out.println("t6 i=10 get(1)預期是無鎖" + ClassLayout.parseInstance(list3.get(1)).toPrintable());//偏向鎖
System.out.println("t6 i=10 get(10) 預期輕量級鎖 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖
}
if (a == list3.get(19)) {
System.out.println("t6 i=19 get(10)預期是無鎖" + 10 + ClassLayout.parseInstance(list3.get(10)).toPrintable());//偏向鎖
System.out.println("t6 i=19 get(19) 滿足重偏向條件20 但類的對象累計撤銷達到40 不可以用偏向鎖了 " + i + ClassLayout.parseInstance(a).toPrintable());//偏向鎖
}
}
}
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t6.start();
Thread.sleep(5000);
System.out.println("由於撤銷鎖次數達到默認的 BiasedLockingBulkRevokeThreshold=40 這里實例化的對象 是無鎖狀態" + ClassLayout.parseInstance(new TestDemo()).toPrintable());//偏向鎖
System.out.println("撤銷偏向后狀態" + 10 + ClassLayout.parseInstance(new TestDemo()).toPrintable());//偏向鎖
}
}
撤銷偏向后狀態10com.boke.TestDemo object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) bf c3 00 f8 (10111111 11000011 00000000 11111000) (-134167617)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
- 當鎖是偏向鎖的時候,被另外的線程所訪問,偏向鎖就會升級為輕量級鎖,其他線程會通過自旋的形式嘗試獲取鎖,不會阻塞,從而提高性能。
- 在代碼進入同步塊的時候,如果同步對象鎖狀態為無鎖狀態(鎖標志位為“01”狀態,是否為偏向鎖為“0”),虛擬機首先將在當前線程的棧幀中建立一個名為鎖記錄(Lock Record)的空間,用於存儲鎖對象目前的Mark Word的拷貝,然后拷貝對象頭中的Mark Word復制到鎖記錄中。
- 拷貝成功后,虛擬機將使用CAS操作嘗試將對象的Mark Word更新為指向Lock Record的指針,並將Lock Record里的owner指針指向對象的Mark Word。
- 如果這個更新動作成功了,那么這個線程就擁有了該對象的鎖,並且對象Mark Word的鎖標志位設置為“00”,表示此對象處於輕量級鎖定狀態。
- 如果輕量級鎖的更新操作失敗了,虛擬機首先會檢查對象的Mark Word是否指向當前線程的棧幀,如果是就說明當前線程已經擁有了這個對象的鎖,那就可以直接進入同步塊繼續執行,否則說明多個線程競爭鎖。
- 若當前只有一個等待線程,則該線程通過自旋進行等待。但是當自旋超過一定的次數,或者一個線程在持有鎖,一個在自旋,又有第三個來訪時,輕量級鎖升級為重量級鎖。
- 多個線程在不同的時間段請求同一把鎖,也就是說沒有鎖競爭。針對這種情形,Java 虛擬機采用了輕量級鎖,來避免重量級鎖的阻塞以及喚醒
- 在沒有鎖競爭的前提下,減少傳統鎖使用OS互斥量產生的性能損耗
- 在競爭激烈時,輕量級鎖會多做很多額外操作,導致性能下降
- 可以認為兩個線程交替執行的情況下請求同一把鎖
public class TestDemo {
}
public class DemoExample9 {
public static void main(String[] args) throws Exception {
TestDemo testDemo = new TestDemo();
//子線程
Thread t1 = new Thread(){
@Override
public void run() {
synchronized (testDemo){
System.out.println("t1 lock ing");
System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
}
}
};
t1.join();
//主線程
synchronized (testDemo){
System.out.println("main lock ing");
System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
}
}
}
- 多個線程競爭同一個鎖的時候,虛擬機會阻塞加鎖失敗的線程,並且在目標鎖被釋放的時候,喚醒這些線程;
- Java 線程的阻塞以及喚醒,都是依靠操作系統來完成的:os pthread_mutex_lock() ;
- 升級為重量級鎖時,鎖標志的狀態值變為“10”,此時Mark Word中存儲的是指向重量級鎖的指針,此時等待鎖的線程都會進入阻塞狀態
分析一個由輕量級鎖膨脹成重量級鎖的案例:
public class TestDemo {
}
public class DemoExample9 {
public static void main(String[] args) throws Exception {
TestDemo testDemo = new TestDemo();
Thread t1 = new Thread(){
@Override
public void run() {
synchronized (testDemo){
System.out.println("t1 lock ing");
System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
}
}
};
t1.start();
synchronized (testDemo){
System.out.println("main lock ing");
System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
}
}
}
運行結果:
-
當進行加鎖操作時,Java 虛擬機會判斷是否已經是重量級鎖。如果不是,它會在當前線程 的當前棧楨中划出一塊空間,作為該鎖的鎖記錄,並且將鎖對象的標記字段復制到該鎖記錄 中。
-
然后,Java 虛擬機會嘗試用 CAS(compare-and-swap)操作替換鎖對象的標記字段。這 里解釋一下,CAS 是一個原子操作,它會比較目標地址的值是否和期望值相等,如果相 等,則替換為一個新的值。
-
假設當前鎖對象的標記字段為 X…XYZ,Java 虛擬機會比較該字段是否為 X…X01。如果 是,則替換為剛才分配的鎖記錄的地址。由於內存對齊的緣故,它的最后兩位為 00。此 時,該線程已成功獲得這把鎖,可以繼續執行了。
-
如果不是 X…X01,那么有兩種可能。第一,該線程重復獲取同一把鎖。此時,Java 虛擬機 會將鎖記錄清零,以代表該鎖被重復獲取。第二,其他線程持有該鎖。此時,Java 虛擬機 會將這把鎖膨脹為重量級鎖,並且阻塞當前線程。
-
當進行解鎖操作時,如果當前鎖記錄(你可以將一個線程的所有鎖記錄想象成一個棧結構, 每次加鎖壓入一條鎖記錄,解鎖彈出一條鎖記錄,當前鎖記錄指的便是棧頂的鎖記錄)的值 為 0,則代表重復進入同一把鎖,直接返回即可。
-
否則,Java 虛擬機會嘗試用 CAS 操作,比較鎖對象的標記字段的值是否為當前鎖記錄的地 址。如果是,則替換為鎖記錄中的值,也就是鎖對象原本的標記字段。此時,該線程已經成
-
功釋放這把鎖。
- 如果不是,則意味着這把鎖已經被膨脹為重量級鎖。此時,Java 虛擬機會進入重量級鎖的釋放過程,喚醒因競爭該鎖而被阻塞了的線程
-
偏向鎖只會在第一次請求時采用 CAS 操作,在鎖對象的標記字段中記錄下當前線程的地 址。在之后的運行過程中,持有該偏向鎖的線程的加鎖操作將直接返回。它針對的是鎖僅會 被同一線程持有的情況。
-
輕量級鎖采用 CAS 操作,將鎖對象的標記字段替換為一個指針,指向當前線程棧上的一塊 空間,存儲着鎖對象原本的標記字段。它針對的是多個線程在不同時間段申請同一把鎖的情 況。
-
重量級鎖會阻塞、喚醒請求加鎖的線程。它針對的是多個線程同時競爭同一把鎖的情況。 Java 虛擬機采取了自適應自旋,來避免線程在面對非常小的 synchronized 代碼塊時,仍 會被阻塞、喚醒的情況。
