1. 程式人生 > 其它 >併發程式設計(上)

併發程式設計(上)

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         };
 8
t2.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 { 21
if (!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物件