1 synchronized简介
学习过java 并发编程的同学都知道 synchronized 关键字是 Java 中用于解决并发情况下数据的同步访问的。用在方法或者代码块上,同一时刻只有一个方法可以进入到临界区,同时它还可以保证共享变量的内存可见性;
synchronized有三个特性:互斥性(确保线程互斥的访问同步代码)、可见性(保证共享变量的修改能够及时可见)、有序性(有效解决重排序问题);
2 字节码层的实现
下面我用一个简单例子来模拟下synchronized在方法上的并发控制。代码的例子如下:
public synchronized void synMethod(){
System.out.println("synchronized method");
}
}
我们可以通过javap- c 我们解析下java后编译的文件,可以清晰的看到如下内容:
public synchronized void synMethod();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String synchronized method
5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 13: 0
line 14: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this LThread/SynchronizedTest;
通过上图我们看到 我们在方法的标志字段上增加了ACC_SYNCHRONIZED ,有了这个标志代码JVM在解释调用这个方法的时候会直接进行同步方法调用。
2.2 代码块的实现机制
下面我用一个简单例子来模拟下synchronized在方法上的并发控制。代码的例子如下:
public void synCode(){
synchronized(this){
System.out.println("synchronized method");
}
}
我们可以通过javap- c 我们解析下java后编译的文件,可以清晰的看到如下内容:
public void synCode();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String synchronized method
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
通过上图我们看到在我们调用打印信息代码前面增加了一个monitorenter指令 在打印信息之后增加了monitorexit的指令。在代码块的情况下就是monitorenter 与monitorexit建立以端代码串行执行的区域,在同一时刻制定有一个对象能执行这一个区域的代码。就相当于机场安检的那个安检门,在同一个时刻只有一个旅客能够通过。
3 JVM层的实现
Java 1.6为了减少synchronized获得锁和释放锁带来相关性能消耗,引入了“偏向锁”和“轻量级锁”的概念。锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。
锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率。下面会对各种锁进行介绍。
3.1 无锁状态
这种状态表示的对象是空闲的状态,不存在线程对对象的竞争。
3.2偏向锁
首先是当前对象是空闲状态,只有一个线程对这个对象进行锁的竞争。或者当前的线程是已经拿到了这个对象的锁,进程二次或者多次加锁的情况。
偏向锁加锁的整个过程只用修改掉对象的mark word的值,重点修改的将偏向锁的标志0改成1,并同时将当前的线程ID设置到线程ID字段中来。原来mark word中年龄等字段保良。注明:关于修改的过程是基于已有的mark word 中年龄,偏向锁状态,线程ID 等参数构造一个新的 mark word ,然后通过CAS整体替换掉老的。
3.3轻量级锁
如果JVM没有开启偏向锁或多个线程竞争偏向锁失败导致偏向锁升级为轻量级锁时,那么会直接走轻量级的锁的获取。
ObjectMonitor 核心的字段如上图中所示,header字段存储的mark word的值 ,object 字段是指向的是同步对象的内存地址,owner是用来存储当前获得锁的线程,cxq就是一个双向链表用来存储等待获取锁线程的队列。
ObjectMonitor的创建过程
1 构建一个空的ObjectMonitor对象监视器,讲同步对象的mark word值复制到header字段中来。从同步对象中的mark word中的值,解析出已有锁的线程设置到owner中来。
2 修改同步对象的的mark word 值进行修改,将前面两位改成10,后面的地址改成ObjectMonitor的地址。
3 将需要等待的线程构构造成ObjectWaiter对象加入到等待队列中。
分隔符