参考:
https://juejin.im/post/6844903830644064264#heading-0
https://juejin.im/post/6844903670933356551#heading-4
在HotSpot虚拟机中,对象的内存布局分为三个区域:
对象头(Header)实例数据(Instance Data)对齐填充(Padding)其中,对象头(Header)又分为两部分:
Mark Word类型指针synchronized用的锁是存储在Java对象头的Mark Word中的。
下面是Mark Word的存储结构(32位JVM):
锁状态25bit4bit1bit,是否是偏向锁2bit,锁标志位无锁状态对象的hashCode对象分代年龄001在运行期,Mark Word里存储的数据会随着标志位的变化而变化。
存储内容标志位状态指向栈中锁记录的指针00轻量级锁指向互斥量(重量级锁)的指针10重量级锁空,不需要记录信息11GC标记偏向线程ID、偏向时间戳、对象分代年龄01偏向锁可以看到,Mark Word包含了线程持有的锁。
JVM基于进入和退出Monitor对象来实现sunchronized方法和代码块的同步,两者细节上有差异。
使用monitorenter和monitorexit指令来实现。
minitorenter指令编译后,插入到同步代码块开始的位置,monitorexit指令编译后,插入到同步代码块结束的位置和异常处。JVM保证每个monitorenter必须有一个monitorexit指令与之对应。
每个对象都有一个Monitor对象(监视器锁)与之对应。
monitorenter当线程执行到monitorenter指令的时候,将会尝试获取Monitor对象的所有权,过程如下:
如果Monitor对象的进入计数器为0,则该线程成功获取Monitor对象的所有权,然后将计数器设置为1。如果该线程已经拥有了Monitor的所有权,那这次算作是重入,重入也会将计数器的值加1。如果其他线程已经占有了Monitor对象,那么该线程进入阻塞状态,直到Monitor的计数器的值为0,再重新尝试获取Monitor对象的所有权。 monitorexit当已经获取Monitor对象所有权的线程执行到monitorexit指令的时候,将会释放Monitor对象的所有权。过程如下:
执行monitorexit指令时,Monitor对象的进入计数器的值减1,如果减1后的值为0,那么这个线程将会释放Monitor对象的所有权,其他被这个Monitor阻塞的线程可以开始尝试去获取这个Monitor对象的所有权。 public class com.fufu.concurrent.SyncCodeBlock { public int i; public com.fufu.concurrent.SyncCodeBlock(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public void syncTask(); Code: 0: aload_0 1: dup 2: astore_1 3: monitorenter //注意此处,进入同步方法 4: aload_0 5: dup 6: getfield #2 // Field i:I 9: iconst_1 10: iadd 11: putfield #2 // Field i:I 14: aload_1 15: monitorexit //注意此处,退出同步方法 16: goto 24 19: astore_2 20: aload_1 21: monitorexit //注意此处,退出同步方法 22: aload_2 23: athrow 24: return Exception table: from to target type 4 16 19 any 19 22 19 any }方法级的同步是隐式的,即无需通过字节码指令来控制,它实现在方法调用和返回操作之中。
JVM可以从 方法常量池 中的 方法表结构(method_info Structure) 中的 ACC_SYNCHRONIZED 访问标志来辨别一个方法是否声明为同步方法。
当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先持有管程,然后再执行方法,最后在方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程获取了管程,其他线程就无法获取管程。
//省略没必要的字节码 //==================syncTask方法====================== public synchronized void syncTask(); descriptor: ()V //方法标识ACC_PUBLIC代表public修饰,ACC_SYNCHRONIZED指明该方法为同步方法 flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=3, locals=1, args_size=1 0: aload_0 1: dup 2: getfield #2 // Field i:I 5: iconst_1 6: iadd 7: putfield #2 // Field i:I 10: return LineNumberTable: line 12: 0 line 13: 10参考:https://www.cnblogs.com/skywang12345/p/3479202.html
下面总结了对象的synchronized基本规则。
规则一:当一个线程访问 “某对象” 的 “synchronized方法” 或者 “synchronized代码块” 时,其他线程对**“该对象” 的这个 “synchronized方法” 或者这个 “synchronized代码块” 的访问**将被阻塞。
规则二:当一个线程访问 “某对象” 的 “synchronized方法” 或者 “synchronized代码块” 时,其他线程对**“该对象” 的其他的 “synchronized方法” 或者其他的 “synchronized代码块” 的访问**将被阻塞。
规则三:当一个线程访问 “某对象” 的 “synchronized方法” 或者 “synchronized代码块” 时,其他线程仍然可以访问 “该对象” 的非同步代码块。
当一个线程访问 “某对象” 的 “synchronized方法” 或者 “synchronized代码块” 时,其他线程对**“该对象” 的这个 “synchronized方法” 或者这个 “synchronized代码块” 的访问**将被阻塞。
public class Demo1 { public static void main(String[] args) { UserRunnable r = new UserRunnable(); Thread t1 = new Thread(r, "thread-1"); Thread t2 = new Thread(r, "thread-2"); t1.start(); t2.start(); } } class UserRunnable implements Runnable { @Override public void run() { synchronized (this) { try { for (int i = 1; i <= 3; i++) { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " loop " + i); } } catch (InterruptedException e) { e.printStackTrace(); } } } }运行结果:
thread-1 loop 1 thread-1 loop 2 thread-1 loop 3 thread-2 loop 1 thread-2 loop 2 thread-2 loop 3 Process finished with exit code 0可以看到,线程thread-1获得了r对象的锁,执行同步代码块,线程thread-2只能等待线程thread-1执行完了才能开始执行。
当一个线程访问 “某对象” 的 “synchronized方法” 或者 “synchronized代码块” 时,其他线程对**“该对象” 的其他的 “synchronized方法” 或者其他的 “synchronized代码块” 的访问**将被阻塞。
public class Demo2 { public static void main(String[] args) { Obj obj = new Obj(); Thread t1 = new Thread(new Runnable() { @Override public void run() { obj.methadA(); } }, "thread-1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { obj.methadB(); } }, "thread-2"); t1.start(); t2.start(); } } class Obj { public void methadA() { synchronized (this) { try { for (int i = 1; i <= 3; i++) { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " call methodA, loop " + i); } } catch (InterruptedException e) { e.printStackTrace(); } } } public void methadB() { synchronized (this) { try { for (int i = 1; i <= 3; i++) { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " call methodB, loop " + i); } } catch (InterruptedException e) { e.printStackTrace(); } } } }运行结果:
thread-1 call methodA, loop 1 thread-1 call methodA, loop 2 thread-1 call methodA, loop 3 thread-2 call methodB, loop 1 thread-2 call methodB, loop 2 thread-2 call methodB, loop 3 Process finished with exit code 0可以看到,Obj类中的methodA和methodB方法都有一个同步代码块。当线程thread-1调用obj对象的methodA方法的时候,线程thread-2被阻塞了,直到thread-1释放了obj对象的锁,thread-2才开始调用methodB方法。
当一个线程访问 “某对象” 的 “synchronized方法” 或者 “synchronized代码块” 时,其他线程仍然可以访问 “该对象” 的非同步代码块。
public class Demo3 { public static void main(String[] args) { Obj obj = new Obj(); Thread t1 = new Thread(new Runnable() { @Override public void run() { obj.methadA(); } }, "thread-1"); Thread t2 = new Thread(new Runnable() { @Override public void run() { obj.methadB(); } }, "thread-2"); t1.start(); t2.start(); } } class Obj { public void methadA() { synchronized (this) { try { for (int i = 1; i <= 3; i++) { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " call methodA, loop " + i); } } catch (InterruptedException e) { e.printStackTrace(); } } } public void methadB() { try { for (int i = 1; i <= 3; i++) { Thread.sleep(1000); System.out.println(Thread.currentThread().getName() + " call methodB, loop " + i); } } catch (InterruptedException e) { e.printStackTrace(); } } }运行结果:
thread-1 call methodA, loop 1 thread-2 call methodB, loop 1 thread-1 call methodA, loop 2 thread-2 call methodB, loop 2 thread-1 call methodA, loop 3 thread-2 call methodB, loop 3 Process finished with exit code 0可以看到,Obj类的methodA方法有同步代码块,而methodB方法没有。当线程thread-1访问methodA方法的时候,线程thread-2可以访问methodB方法,不会阻塞。
实例锁:
锁在某一个实例对象上。如果该类是单例,那么该锁也具有全局锁的概念。实例锁对应的就是 synchronized关键字。全局锁:
该锁针对的是类,无论实例多少个对象,线程都共享该锁。全局锁对应的就是 static synchronized关键字(或者是锁在该类的class或者lassloader对象上)。例子:
pulbic class Something { public synchronized void syncA(){} public synchronized void syncB(){} public static synchronized void cSyncA(){} public static synchronized void cSyncB(){} }假设Something有两个实例x和y,结论:
x.syncA()和x.syncB()不能被同时访问。因为使用了同一个对象的实例锁。x.syncA()和y.syncB()可以被同时访问。因为使用了不同实例对象的实例锁。x.cSyncA()和y.cSyncB()不能被同时访问。因为他们使用了同一个全局锁,相当于Something类的锁。x.syncA()和Something.cSyncA()可以被同时访问。因为一个是实例x的锁,一个是类Something的锁,不是同一个锁,互不干扰。