synchronized解析二:原理解析

问题

(1)synchronized的特性?

(2)synchronized的实现原理?

(3)synchronized是否可重入?

(4)synchronized是否是公平锁?

(5)synchronized的优化?

(6)synchronized的五种使用方式?

 

synchronized的实现原理

在Java内存模型中8种原子的指令:lock,unlock,read,load,use,assign,store,write。

lock:锁定,作用于主内存的变量,它把主内存中的变量标识为一条线程独占状态。

unlock:解锁,作用于主内存的变量,它把锁定的变量释放出来,释放出来的变量才可以被其它线程锁定。

但是这两个指令并没有直接提供给用户使用,而是提供了两个更高层次的指令 monitorenter 和 monitorexit 来隐式地使用 lock 和 unlock 指令。synchronized的底层原理就是,在同步块的前后分别生成 monitorenter 和 monitorexit 字节码指令来实现。

在执行monitorenter指令的时候,首先要去尝试获取对象的锁,如果这个对象没有被锁定,或者当前线程已经拥有了这个对象的锁,就把锁的计数器加1,相应地,在执行monitorexit的时候会把计数器减1,当计数器减小为0时,锁就释放了。

看看实际代码中synchronized编译后长啥样

public static void SynchronizedTest(){
  //对PropertyValue对象加了两次synchronized
        synchronized (PropertyValue.class){
            synchronized (PropertyValue.class){
            }
        }
    }

编译后

 0 ldc #4 <com/own/common/learn/springframework/beans/PropertyValue>
 2 dup
 3 astore_0
   //调用monitorenter,它的参数变量0
 4 monitorenter
 5 ldc #4 <com/own/common/learn/springframework/beans/PropertyValue>
 7 dup
 8 astore_1
   //调用monitorenter,它的参数是变量1
 9 monitorenter
10 aload_1
   //调用monitorexit解锁,它的参数是上面加载的变量1
11 monitorexit
12 goto 20 (+8)
15 astore_2
16 aload_1
17 monitorexit
18 aload_2
19 athrow
20 aload_0
   //调用monitorexit解锁,它的参数是上面加载的变量0
21 monitorexit
22 goto 30 (+8)
25 astore_3
26 aload_0
27 monitorexit
28 aload_3
29 athrow
30 return

​ 从编译后的代码可以看出,加锁是先加索变量0然后加索变量1,解锁的时候正好是相反的顺序,先解锁变量1,再解锁变量0,所以synchronized是可重入的。

 

原子性、可见性、有序性

​ synchronized关键字底层是通过monitorenter和monitorexit实现的,而这两个指令又是通过lock和unlock来实现的。

通过Java内存模型中lock和unlock的规则我们知道:

  • lock和unlock之间的代码,同一时刻只允许一个线程访问,所以,synchronized是具有原子性的。
  • lock和unlock时都会从主内存加载变量或把变量刷新回主内存,而lock和unlock之间的变量是不会被其它线程修改的,所以,synchronized是具有可见性的。
  • 变量的加锁都要排队进行,且其它线程不允许解锁当前线程锁定的对象,所以,synchronized是具有有序性的。

 

非公平锁和可重入锁

由上面编译的字节码看出synchronized是可重入锁。而且是非公平锁,可以通过简单代码实验下

public class SynchronizedTest {

    public static void sync(String tips) {
        synchronized (SynchronizedTest.class){
            System.out.println(tips);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        new Thread(()->sync("线程1")).start();
        new Thread(()->sync("线程2")).start();
        new Thread(()->sync("线程3")).start();
        new Thread(()->sync("线程4")).start();
    }

运行结果不是顺序的1234,所以synchronized是非公平锁。

 

锁优化

jdk8对synchronized进行了优化

  1. 偏向锁,是指一段同步代码一直被一个线程访问,那么这个线程会自动获取锁,降低获取锁的代价。
  2. 轻量级锁,是指当锁是偏向锁时,被另一个线程所访问,偏向锁会升级为轻量级锁,这个线程会通过自旋的方式尝试获取锁,不会阻塞,提高性能。
  3. 重量级锁,是指当锁是轻量级锁时,当自旋的线程自旋了一定的次数后,还没有获取到锁,就会进入阻塞状态,该锁升级为重量级锁,重量级锁会使其他线程阻塞,性能降低。

 

总结

(1)synchronized在编译时会在同步块前后生成monitorenter和monitorexit字节码指令;

(2)monitorenter和monitorexit字节码指令需要一个引用类型的参数,基本类型不可以哦;

(3)monitorenter和monitorexit字节码指令更底层是使用Java内存模型的lock和unlock指令;

(4)synchronized是可重入锁;

(5)synchronized是非公平锁;

(6)synchronized可以同时保证原子性、可见性、有序性;

(7)synchronized有三种状态:偏向锁、轻量级锁、重量级锁;

posted @ 2021-06-10 19:35  gaowei656  阅读(55)  评论(0)    收藏  举报