并发编程(上)
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对象