synchronized在JDK1.5之前是一个重量级锁,相当于JUC里面的lock锁,但是在1.6版本及之后对它做了很大的升级和优化,它不在那么的笨重了。
synchronized的作用主要有三个:
原子性:确保线程互斥的访问同步代码。
可见性:保证共享变量的修改能够及时可见
有序性:有效解决重排序问题,即一个unlock操作先行发生于后面对同一个锁lock操作。
synchronized的使用
synchronized的三种使用方式:
- 修饰实例方法:作用于当前实例加锁。
代码里面是在方法前面加上synchronized,就是publich后加上synchronized,代码实例如下:
public synchronized getMethod(){
// todo 实现方法
}
修饰静态方法:作用于当前类对象加锁
静态方法是属于类而不是对象的。同样的synchronized修饰的静态方法说的是这个类的所有对象
public synchronized static void getMethod() {
// todo
}
修饰代码块:指定加锁对象,对给定对象加锁。
一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。
synchronize的底层实现
在JVM中对象在内存的布局分为三块区域:对象头,实例数据和对齐填充。那synchronized的底层实现,锁就是存在对象头里的,那为什么是对象头呢?hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段),class pointer(类型指针)。其中class point是对象指向它的类元素的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。Mark Word用于存储对象自身运行时的数据,它是实现轻量级锁和偏向锁的关键。
synchronized在JVM里面实现都是基于进入和退出monitor对象来实现方法同步和代码块同步的,具体细节都可以通过对monitorEnter和monitorEixt实现的。
monitorEnter指令:插入在代码块的开始位置,当代码执行带该指令时,将会尝试获取对象monitor的所有权,即尝试获得该对象锁。
monitorExit指令:插入在方法结束和异常出,JVM保证每个monitorEnter都有对应的monitorExit
Synchronized锁升级的顺序
Synchronized在1.6之前是重量级锁,如果第一个线程使用了它,下一个线程想要使用就得等它释放了才能使用。如果发成阻塞了就得一直等待下去,所以效率低下。
1.6之后做了很大的优化,有了锁升级的过程。依次是:无锁状态,偏向锁状态,轻量级锁状态,重量级锁状态。锁是单向执行只能升级,不能降级的。这个过程也是开销逐渐加大的过程。
一开始如果是一个线程的时候是无锁状态,下一次同一个线程进来还是无锁状态,如果有其他线程跟第一个线程进行竞争就会升级为CAS偏向锁,若锁竞争激烈就会升级会轻量级锁,最后升级为重量级锁。