前言
并发变成中,synchronized一直都是元老级角色,它一直被称为重量级锁,在1.6中对synchronized进行各种优化过后才改善了它的性能消耗。为了降低synchronized的性能消耗引入了偏向锁,轻量级锁,以及JVM底层对锁存储结构的更改。
synchronized的使用
java中一切皆对象,每一个对象都可以作为锁,这是synchronized实现的基础,一般都隐氏的指定了锁的对象,所以平时使用时并没有指定参数,但是也可以显示的指定。
synchronized的使用如下:
- 普通同步方法:锁是当前的实例对象,进入同步方法需要获取实例对象锁
- 静态同步方法:锁是Class对象,需要获取Class对象锁才能访问
- 同步方法块:锁是括号里的对象
public void test(){
Integer a =new Integer(1);
//显示指定锁的对象
synchronized (a){
}
}
//静态方法,锁Class对象
public synchronized static void get(){
}
//实例方法,锁实例对象
public synchronized void set(){
}
synchronized原理
synchronized在JVM层的实现是基于monitor的进入与退出的,monitor可以翻译成监视器或者管程,不过管程模型大家更加的熟悉,代码块的同步基于monitorenter和monitorexit指令,指令在编译后插入到相应的位置,这对指令必须成对存在。任何的对象都有一个对应的monitor对象,monitor被持有后,处于锁定状态。线程执行指令会尝试获取monitor的所有权,也就是获取对象的锁。
各位了解了对象的内存布局就知道对象头中存储着锁相关的信息,持有不同的锁,对象头的mark word内容不同,不过我一直有个疑问,monitor对象是用c++写的,monitor是存在哪里的呢?留着这个问题,继续往下说,JDK1.6前,synchronized是不推荐使用的,被称为重量级锁,1.6进行了各种优化,引入了级别从低到高的偏向锁、轻量级锁、重量级锁
偏向锁
大多数情况下,锁不仅不存在多线程竞争,而且总是同一线程多次获取,为了降低获取锁的代价,引入偏向锁。这里说下CAS,compareandswap,是一个原子操作,用来更新值,举个例子:如果 int a = 1,多个线程需要更改a的值,使用CAS能够解决并发问题主要包含以下操作,方法内,获取a的值赋值给c,即将要赋予a的值为b,先比较c与a是否相等,相等则a = b,否则其他线程改了a的值,重新进行cas操作。
线程访问同步块并获取锁时,在对象头和栈桢锁记录中保存锁偏向的线程ID,当本线程再次获取锁时,就不需要CAS的加锁和解锁,看一下mark word是否有指向当前线程的偏向锁。如果没有,看下mark word中是否是偏向锁,如果是使用CAS修改偏向锁修改为当前线程,如果不是偏向锁,使用CAS竞争锁,下图是流程:
轻量级锁
线程执行同步代码块之前,JVM将当前线程的栈桢中创建存储锁记录的空间,将对象头的mark word复制到锁记录,然后线程通过CAS将mark word替换为指向锁记录的指针,成功则获取到锁,失败自旋获取。流程图如下: