并发编程(上)

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对象

 

posted @ 2022-04-02 21:59  CarBlack  阅读(37)  评论(0编辑  收藏  举报