目录
从锁策略的角度,可以分析出synchronized具有以下特点:
(1) 开始是乐观锁,锁冲突激烈转化为悲观锁;
(2) 开始是轻量级锁,如果锁被持有的时间较长,就转换为重量级锁;
(3) 轻量级锁的部分是基于自旋锁实现的,重量级锁的部分是基于挂起等待锁实现的;
(4) 不是读写锁,是普通互斥锁;
(5) 是非公平锁;
(6) 是可重入锁。
JVM将synchronized分为无锁、偏向锁、轻量级锁、重量级锁几种状态,根据不同情况,状态会进行升级。
无锁,即不加锁的状态。
偏向锁:
偏向锁类似于单例模式中的“懒汉模式”,必要的时候才会真正加锁。
偏向锁会给锁对象设置一个标志位,记录这个锁属于哪个线程:
如果后续没有其他线程来竞争这个锁,那么就不需要真加锁;
如果后续有其他线程来竞争这个锁,那么就取消偏向锁的状态,进入轻量级锁的状态。
举个栗子:两个线程同时进行count++操作
- public class Test {
- private static int count;//计数器
- private static Object locker = new Object();//锁对象
-
- //自增方法
- public static void getAndIncrement(){
- synchronized (locker){
- count++;
- }
- }
-
- public static void main(String[] args) throws InterruptedException {
- Thread t1 = new Thread(() -> {
- for (int i = 0; i < 50000; i++) {
- getAndIncrement();
- }
- });
- Thread t2 = new Thread(() -> {
- for (int i = 0; i < 50000; i++) {
- getAndIncrement();
- }
- });
- t1.start();
- t2.start();
-
- t1.join();
- t2.join();
- System.out.println("count = "+count);
- }
- }
如果两个线程在调度的时候恰好错开了,就不会真加锁,如下图:

如果两个线程在调度的时候没有完全错开,只有当t2执行到lock时,t1的lock才真正生效:

轻量级锁:
当线程之间产生锁竞争时,就会升级到轻量级锁状态。
重量级锁:
当线程之间锁竞争激烈时,就会升级到重量级锁状态。
JVM会对我们写的代码进行判定,如果发现某段代码不必加锁,但又使用了synchronized的时候,就会直接消除这个锁。
比如说,在单线程环境执行某一段加锁的代码时,就没必要加锁~
或者多个线程各自获取不同的锁对象时,也没必要加锁~
锁粗化,就是把细粒度的锁变为粗粒度的锁。
synchronized代码块中包含的代码少,则粒度细;包含的代码多,则粒度粗。
如果发现代码中频繁地进行无意义的加锁操作,就会进行锁粗化~
当然,粗化的前提是保证代码的逻辑在粗化前后是不变的~

举个栗子:给领导打电话汇报工作时:
方式一:
打电话,汇报工作1,挂电话;
打电话,汇报工作2,挂电话;
打电话,汇报工作3,挂电话。
方式二:
打电话,汇报工作1,汇报工作2,汇报工作3,挂电话。
显然,方式二更加高效~
synchronized背后竟然做了这么多的优化~
设计JVM的大佬,为了简化synchronized的使用,真是为我这种菜鸡操碎了心555~