• 并发编程(上)


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

  • 相关阅读:
    .hpp文件
    最小高度的BST
    检查图中的有向路径
    c++ 对象内存布局详解
    链表求差
    offer--链表反转和从尾到头打印链表
    平衡二叉树的判断
    java 抽象类和接口
    原型模式--prototype
    装饰模式decorator
  • 原文地址:https://www.cnblogs.com/carblack/p/16091350.html
Copyright © 2020-2023  润新知