併發程式設計(上)
1、執行緒的實現
Java是一種面向物件的語言,只能對使用者態的資源進行操作,無法對核心態的資源操作,但是建立執行緒、加鎖等操作都需要核心態的資源來實現,
所以Java需要利用JNI呼叫本地方法來操作核心態資源。
1 //建立執行緒並執行 2 Thread t2 = new Thread(){ 3 @Override 4 public void run(){ 5 System.out.println("Thread is OK"); 6 } 7 }; 8t2.start(); 9 10 //start方法實現 11 public synchronized void start() { 12 if (threadStatus != 0) 13 throw new IllegalThreadStateException(); 14 group.add(this); 15 boolean started = false; 16 try { 17 start0(); 18 started = true; 19 } finally { 20 try { 21if (!started) { 22 group.threadStartFailed(this); 23 } 24 } catch (Throwable ignore) { 25 } 26 } 27 } 28 //本地方法 29 private native void start0();
下面通過start0()方法來呼叫作業系統中的建立執行緒方法:
1 //JDK中src\java.base\share\native\libjava\Thread.c通過start0()方法來確定要呼叫什麼2 static JNINativeMethod methods[] = { 3 {"start0", "()V", (void *)&JVM_StartThread}, 4 {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread}, 5 {"isAlive", "()Z", (void *)&JVM_IsThreadAlive}, 6 {"suspend0", "()V", (void *)&JVM_SuspendThread}, 7 {"resume0", "()V", (void *)&JVM_ResumeThread}, 8 {"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority}, 9 {"yield", "()V", (void *)&JVM_Yield}, 10 {"sleep", "(J)V", (void *)&JVM_Sleep}, 11 {"currentThread", "()" THD, (void *)&JVM_CurrentThread}, 12 {"countStackFrames", "()I", (void *)&JVM_CountStackFrames}, 13 {"interrupt0", "()V", (void *)&JVM_Interrupt}, 14 {"isInterrupted", "(Z)Z", (void *)&JVM_IsInterrupted}, 15 {"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock}, 16 {"getThreads", "()[" THD, (void *)&JVM_GetAllThreads}, 17 {"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads}, 18 {"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName}, 19 };
最後一步步呼叫到真實的執行緒建立方法:
1 //pthread_t*thread ,傳出引數,呼叫之後會傳出被建立執行緒的id 2 //const pthread_attr_t*attr,執行緒屬性 3 //void *(*start_routine) (void *),執行緒啟動後的主體函式 4 //*arg,主體函式的引數 5 int pthread_create(pthread_t *thread, const pthread_attr_t *attr, 6 void *(*start_routine) (void *), void *arg);
此時,執行緒建立完成。
同理,執行緒加鎖也是如此,Java物件呼叫本次方法操作使用者態資源進行加鎖:
作業系統加鎖:pthread_Mutex_Lock(&Mutex)
作業系統解鎖:pthread_Mutex_UnLock(&Mutex)
作業系統自旋鎖:pthread_Spim_Lock(&Mutex)
使用者態和核心態是一種對CPU操作的許可權,CPU的操作許可權分為0-3四種執行級別,其中0是核心態,3是使用者態。
使用者態切換到核心態的3種方式
1). 系統呼叫
2). 異常(這個異常不是java當中的異常)
3). 外圍裝置的中斷
2.synchronized關鍵字
併發程式設計的三大特性:原子性,操作不可打斷,要不全部成功,要不全部失敗
有序性,本執行緒內,所有的操作都是有序的
可見性,一個執行緒修改了共享變數後,其他執行緒能夠立即得知這個修改
sync關鍵字使用:
1 Object o = new Object(); 2 System.out.println("synchronized未加鎖狀態:"); 3 System.out.println(ClassLayout.parseInstance(o).toPrintable()); 4 //sync對o物件加鎖 5 //加鎖之後會改變o物件的物件頭 6 synchronized (o){ 7 System.out.println("synchronized加鎖狀態:"); 8 System.out.println(ClassLayout.parseInstance(o).toPrintable()); 9 }
//引入jar包,列印物件頭資訊 <dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency> //執行程式碼後,物件頭資訊 synchronized未加鎖狀態: java.lang.Object 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) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total synchronized加鎖狀態: java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 58 f3 51 03 (01011000 11110011 01010001 00000011) (55702360) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 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變成了00輕量鎖。
一個物件是由物件頭、例項資料、對齊填充組成的,物件頭大小為12bytes,按照二進位制儲存,一共是96bit
物件頭由64bit的markword+32bit的Kclass Point組成:
無鎖時,無hash,可偏向,偏向標識為1,鎖狀態為01,物件頭資訊:
//JVM中預設開啟偏向延遲,使用-XX:BiasedLockingStartupDelay=0關閉偏向延遲
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
無鎖時,有hash,不可偏向,偏向標識為0,鎖狀態為01,物件頭資訊:
//System.out.println("hashCode:"+Integer.toHexString(o.hashCode()))進行hash計算
hashCode:3d646c37
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 37 6c 64 (00000001 00110111 01101100 01100100) (1684813569)
4 4 (object header) 3d 00 00 00 (00111101 00000000 00000000 00000000) (61)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
鎖物件第一次被持有,此時為偏向鎖,偏向標識為1,鎖狀態為01,物件頭資訊:
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e0 73 01 (00000101 11100000 01110011 00000001) (24371205)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
兩條執行緒對同一個鎖物件進行交替加鎖,此時為輕量鎖,鎖狀態為00,物件頭資訊:
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 58 f3 51 03 (01011000 11110011 01010001 00000011) (55702360)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
兩條執行緒對同一個鎖物件進行資源競爭加鎖,此時為重量鎖,鎖狀態為10,物件頭資訊:
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) da 4b 42 03 (11011010 01001011 01000010 00000011) (54676442)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
sync關鍵字實現方式:
JVM通過物件監視器(monitor)來實現對方法進行同步的,程式碼編譯之後會在同步方法之前加入monitor.enter指令,同時會在方法結束和異常中加入monitor.exit指令,執行緒獲取到鎖,執行同步方法,最後執行monitor.exit指令釋放鎖資源,
若此時有別的執行緒來獲取鎖,因為所被佔據獲取失敗,這條執行緒會阻塞進入同步佇列等待,直到佔據鎖的執行緒執行完畢,同步佇列中的執行緒會被喚醒,獲取鎖資源,執行同步方法。
作業系統層面中物件監視器依賴於互斥鎖(Mutex Lock)來實現。
1 /* 2 * 測試加鎖方法 3 */ 4 public static void testLock(){ 5 6 System.out.println(Thread.currentThread().getName()+"物件初始狀態:"); 7 System.out.println(ClassLayout.parseInstance(o).toPrintable()); 8 9 // System.out.println(str+"物件hashCode狀態:"); 10 // System.out.println("hashCode:"+Integer.toHexString(o.hashCode())); 11 // System.out.println(ClassLayout.parseInstance(o).toPrintable()); 12 synchronized (o){ 13 System.out.println(Thread.currentThread().getName()+"物件加鎖狀態:"); 14 System.out.println(ClassLayout.parseInstance(o).toPrintable()); 15 } 16 }
測試程式碼對應的指令程式碼:
0 getstatic #10 <java/lang/System.out> 3 new #11 <java/lang/StringBuilder> 6 dup 7 invokespecial #12 <java/lang/StringBuilder.<init>> 10 invokestatic #13 <java/lang/Thread.currentThread> 13 invokevirtual #14 <java/lang/Thread.getName> 16 invokevirtual #15 <java/lang/StringBuilder.append> 19 ldc #16 <物件初始狀態:> 21 invokevirtual #15 <java/lang/StringBuilder.append> 24 invokevirtual #17 <java/lang/StringBuilder.toString> 27 invokevirtual #18 <java/io/PrintStream.println> 30 getstatic #10 <java/lang/System.out> 33 getstatic #19 <Math/Test.o> 36 invokestatic #20 <org/openjdk/jol/info/ClassLayout.parseInstance> 39 invokevirtual #21 <org/openjdk/jol/info/ClassLayout.toPrintable> 42 invokevirtual #18 <java/io/PrintStream.println> 45 getstatic #19 <Math/Test.o> 48 dup 49 astore_0 50 monitorenter 51 getstatic #10 <java/lang/System.out> 54 new #11 <java/lang/StringBuilder> 57 dup 58 invokespecial #12 <java/lang/StringBuilder.<init>> 61 invokestatic #13 <java/lang/Thread.currentThread> 64 invokevirtual #14 <java/lang/Thread.getName> 67 invokevirtual #15 <java/lang/StringBuilder.append> 70 ldc #22 <物件加鎖狀態:> 72 invokevirtual #15 <java/lang/StringBuilder.append> 75 invokevirtual #17 <java/lang/StringBuilder.toString> 78 invokevirtual #18 <java/io/PrintStream.println> 81 getstatic #10 <java/lang/System.out> 84 getstatic #19 <Math/Test.o> 87 invokestatic #20 <org/openjdk/jol/info/ClassLayout.parseInstance> 90 invokevirtual #21 <org/openjdk/jol/info/ClassLayout.toPrintable> 93 invokevirtual #18 <java/io/PrintStream.println> 96 aload_0 97 monitorexit 98 goto 106 (+8) 101 astore_1 102 aload_0 103 monitorexit 104 aload_1 105 athrow 106 return
sync關鍵字執行流程:
sync特點:
1)保證了原子性、可見性、有序性
執行的方法是不能被打斷
獲取鎖時,從記憶體中讀取最新的資料,釋放鎖時,所有寫入都會寫回記憶體
同步佇列是一個連結串列,按順序喚醒,執行同步方法
2)支援鎖的重入,同一個執行緒獲取鎖後,執行其他同樣鎖的程式碼時可以直接使用
呼叫sync關鍵字時,先判斷呼叫的執行緒id是否與當前持有鎖的執行緒id一致,若是,計數加1,直接執行同步方法,若不是,進入等待佇列
3)是一個重量級鎖,效率比較低
底層實現依賴於作業系統的互斥鎖(Mutex Lock)來實現,使用時需要從使用者態切換到核心態,效率比較低,但是JDK1.6中對sync關鍵字進行了優化,引入了偏向鎖和輕量鎖,
偏向鎖升級到輕量鎖時,是通過CAS來操作的,無需呼叫作業系統的互斥鎖,只有發生資源競爭的時候,由輕量鎖升級到重量鎖,才需要作業系統的互斥鎖參加
sync加鎖和鎖膨脹的過程:
JVM遇到monitorenter指令後會進行相應的加鎖操作:
1 //src/hotspot/share/interpreter/bytecodeInterpreter.cpp 2 //遇到monitorenter進行加鎖操作 3 CASE(_monitorenter): { 4 oop lockee = STACK_OBJECT(-1); 5 // derefing's lockee ought to provoke implicit null check 6 CHECK_NULL(lockee);
// BaseicObjectLock,java中稱為lockRecord,有兩個屬性,displacedWord,用來存放鎖物件中的markWord,另一個Obj ref,用來存放鎖物件的地址,這時,鎖物件中的markWord就會變成62bit的指向lockRecord的指標+2bit的鎖標識
10 BasicObjectLock* limit = istate->monitor_base(); 11 BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base(); 12 BasicObjectLock* entry = NULL; 13 while (most_recent != limit ) { 14 if (most_recent->obj() == NULL) entry = most_recent; 15 else if (most_recent->obj() == lockee) break; 16 most_recent++; 17 } 18 if (entry != NULL) { 19 entry->set_obj(lockee); 20 int success = false; 21 uintptr_t epoch_mask_in_place = (uintptr_t)markOopDesc::epoch_mask_in_place; 22 23 markOop mark = lockee->mark(); 24 intptr_t hash = (intptr_t) markOopDesc::no_hash; 25 // implies UseBiasedLocking 26 if (mark->has_bias_pattern()) { 27 uintptr_t thread_ident; 28 uintptr_t anticipated_bias_locking_value; 29 thread_ident = (uintptr_t)istate->thread(); 30 anticipated_bias_locking_value = 31 (((uintptr_t)lockee->klass()->prototype_header() | thread_ident) ^ (uintptr_t)mark) & 32 ~((uintptr_t) markOopDesc::age_mask_in_place); 33 34 if (anticipated_bias_locking_value == 0) { 35 // already biased towards this thread, nothing to do 36 if (PrintBiasedLockingStatistics) { 37 (* BiasedLocking::biased_lock_entry_count_addr())++; 38 } 39 success = true; 40 } 41 else if ((anticipated_bias_locking_value & markOopDesc::biased_lock_mask_in_place) != 0) { 42 // try revoke bias 43 markOop header = lockee->klass()->prototype_header(); 44 if (hash != markOopDesc::no_hash) { 45 header = header->copy_set_hash(hash); 46 } 47 if (lockee->cas_set_mark(header, mark) == mark) { 48 if (PrintBiasedLockingStatistics) 49 (*BiasedLocking::revoked_lock_entry_count_addr())++; 50 } 51 } 52 else if ((anticipated_bias_locking_value & epoch_mask_in_place) !=0) { 53 // try rebias 54 markOop new_header = (markOop) ( (intptr_t) lockee->klass()->prototype_header() | thread_ident); 55 if (hash != markOopDesc::no_hash) { 56 new_header = new_header->copy_set_hash(hash); 57 } 58 if (lockee->cas_set_mark(new_header, mark) == mark) { 59 if (PrintBiasedLockingStatistics) 60 (* BiasedLocking::rebiased_lock_entry_count_addr())++; 61 } 62 else { 63 CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception); 64 } 65 success = true; 66 } 67 else { 68 // try to bias towards thread in case object is anonymously biased 69 markOop header = (markOop) ((uintptr_t) mark & ((uintptr_t)markOopDesc::biased_lock_mask_in_place | 70 (uintptr_t)markOopDesc::age_mask_in_place | 71 epoch_mask_in_place)); 72 if (hash != markOopDesc::no_hash) { 73 header = header->copy_set_hash(hash); 74 } 75 markOop new_header = (markOop) ((uintptr_t) header | thread_ident); 76 // debugging hint 77 DEBUG_ONLY(entry->lock()->set_displaced_header((markOop) (uintptr_t) 0xdeaddead);) 78 if (lockee->cas_set_mark(new_header, header) == header) { 79 if (PrintBiasedLockingStatistics) 80 (* BiasedLocking::anonymously_biased_lock_entry_count_addr())++; 81 } 82 else { 83 CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception); 84 } 85 success = true; 86 } 87 } 88 89 // traditional lightweight locking 90 if (!success) { 91 markOop displaced = lockee->mark()->set_unlocked(); 92 entry->lock()->set_displaced_header(displaced); 93 bool call_vm = UseHeavyMonitors; 94 if (call_vm || lockee->cas_set_mark((markOop)entry, displaced) != displaced) { 95 // Is it simple recursive case? 96 if (!call_vm && THREAD->is_lock_owned((address) displaced->clear_lock_bits())) { 97 entry->lock()->set_displaced_header(NULL); 98 } else { 99 CALL_VM(InterpreterRuntime::monitorenter(THREAD, entry), handle_exception); 100 } 101 } 102 } 103 UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1); 104 } else { 105 istate->set_msg(more_monitors); 106 UPDATE_PC_AND_RETURN(0); // Re-execute 107 } 108 } 109 //遇到monitorexit進行解鎖操作 110 CASE(_monitorexit): { 111 oop lockee = STACK_OBJECT(-1); 112 CHECK_NULL(lockee); 113 // BaseicObjectLock,java中稱為lockRecord,有兩個屬性,displacedWord,用來存放鎖物件中的markWord,另一個Obj ref,用來存放鎖物件的地址 115 BasicObjectLock* limit = istate->monitor_base(); 116 BasicObjectLock* most_recent = (BasicObjectLock*) istate->stack_base(); 117 while (most_recent != limit ) { 118 if ((most_recent)->obj() == lockee) { 119 BasicLock* lock = most_recent->lock(); 120 markOop header = lock->displaced_header(); 121 most_recent->set_obj(NULL); 122 if (!lockee->mark()->has_bias_pattern()) { 123 bool call_vm = UseHeavyMonitors; 124 // If it isn't recursive we either must swap old header or call the runtime 125 if (header != NULL || call_vm) { 126 markOop old_header = markOopDesc::encode(lock); 127 if (call_vm || lockee->cas_set_mark(header, old_header) != old_header) { 128 // restore object for the slow case 129 most_recent->set_obj(lockee); 130 CALL_VM(InterpreterRuntime::monitorexit(THREAD, most_recent), handle_exception); 131 } 132 } 133 } 134 UPDATE_PC_AND_TOS_AND_CONTINUE(1, -1); 135 } 136 most_recent++; 137 }
偏向鎖加鎖的流程:
執行緒t1對鎖物件進行加鎖
解鎖時,將displacedWord中複製的鎖物件MarkWord資訊複製到鎖物件的markWord中,刪除執行緒本地私有棧中的lockRecord
輕量鎖加鎖過程:
執行緒ti加鎖執行完釋放鎖,執行緒2再來加鎖,此時升級為輕量鎖
輕量鎖膨脹為重量鎖的過程:
t2未釋放鎖物件,t3來獲取鎖物件,此時鎖物件中markWord處於有鎖狀態,且指標未指向t3,發生資源競爭,輕量鎖膨脹為重量鎖
1)呼叫omAlloc分配一個Monitor物件,
2)初始化Monitor物件
3)將Monitor物件狀態設定為膨脹中(INFLATING)
4)設定Monitor物件的header屬性為displacedWord,owner屬性為lockRecord,obj欄位為鎖物件地址
5)CAS設定鎖物件中markWord為重量鎖狀態,並指向第一步分配的Monitor物件