Synchronized 关键字详解

2021.01.09 07:01 19
阅读约 7 分钟

Synchronized 关键字底层是如何实现的?

线程安全,我认为更多的是描述一种风险。在堆内存中的数据由于可以被任何线程访问到,在没有任何限制的情况下存在被意外修改的风险。而synchronized是通过对共享资源加锁的方式,使同一时间只能有一个线程能够访问到临界区(也就是共享资源),共享资源包括了方法、锁代码块和对象。

那是不是使用了synchronized就一定能保证线程安全呢?不是的,如果不能正确的使用,很可能就会引发死锁,所以,保证线程安全的前提是正确的使用synchronized

来看一下synchronized实现同步的基础:Java中的每一个对象都可以称为锁。具体表现为:

  • 对于普通同步方法,锁是当前实例对象。
  • 对于静态方法,锁是当前类class对象。
  • 对于同步方法块,锁是synchronized括号里配置的对象。

从JVM规范中可以看到synchronized在JVM里的实现原理,JVM基于进入和退出 Monitor 对象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用 monitorentermonitorexit 指令实现的,而方法同步是使用另外一种方式实现的。

monitorenter 指令是在编译后插入到同步代码块的开始位置,而 monitorexit 指令则插入在方法结束处和异常处,JVM 要保证每个 monitorenter 必须有对应的 monitorexit 与之配对。任何对象都有一个monitor与之关联,当且一个 monitor 被持有后,它将处于锁定状态。线程执行到 monitorenter 指令时,将会尝试获取对象所对应的 monitor 的所有权,即尝试获取对象的锁。

Synchronized对应的字节码

public static void main(java.lang.String[]);
 descriptor: ([Ljava/lang/String;)V
 flags: ACC_PUBLIC, ACC_STATIC
 Code:
 stack=2, locals=3, args_size=1
 0: getstatic #2   // <- lock引用 (synchronized开始)
 3: dup
 4: astore_1       // lock引用 -> slot 1
 5: monitorenter   // 将 lock对象 MarkWord 置为 Monitor 指针
 6: getstatic #3   // <- i
 9: iconst_1       // 准备常数 1
 10: iadd          // +1
 11: putstatic #3  // -> i
 14: aload_1       // <- lock引用
 15: monitorexit   // 将 lock对象 MarkWord 重置, 唤醒 EntryList
 16: goto 24
 19: astore_2      // e -> slot 2
 20: aload_1       // <- lock引用
 21: monitorexit   // 将 lock对象 MarkWord 重置, 唤醒 EntryList
 22: aload_2       // <- slot 2 (e)
 23: athrow        // throw e
 24: return
 Exception table:
 from to target type
 	 6  16  19    any
 	19  22  19    any
 LineNumberTable:
 line 8: 0
 line 9: 6
 line 10: 14
 line 11: 24
 LocalVariableTable:
 Start Length Slot Name Signature
 0 25 0 args [Ljava/lang/String;
 StackMapTable: number_of_entries = 2
 frame_type = 255 /* full_frame */
 offset_delta = 19
 locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
 stack = [ class java/lang/Throwable ]
 frame_type = 250 /* chop */
 offset_delta = 4

Synchronized与 Lock 相比优缺点分别是什么?

与synchronized不同的是lock是纯java手写的,与底层的JVM无关。在java.util.concurrent.locks包中有很多Lock的实现类,常用的有ReenTrantLock、ReadWriteLock(实现类有ReenTrantReadWriteLock),其实现都依赖java.util.concurrent.AbstractQueuedSynchronizer类(简称AQS),实现思路都大同小异。

① Lock和Synchronized的区别

(1)lock是一个接口,而synchronized是java的关键字,synchronized是内置的语言实现;

(2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象,因此使用lock()时需要在finally块中释放锁;

(3)lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去。

(4)通过lock可以知道有没有成功获取锁,而synchronized却无法办到

(5)lock可以提高多个线程进行读操作的效率

在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而竞争资源非常激烈是(既有大量线程同时竞争),此时lock的性能要远远优于synchronized。

② Lock和Synchronized的比较

 1.synchronized

  优点:实现简单,语义清晰,便于JVM堆栈跟踪,加锁解锁过程由JVM自动控制,提供了多种优化方案,使用更广泛

  缺点:

  1、当线程尝试获取锁的时候,如果获取不到锁就会一直阻塞。

  2、如果占有锁的线程进入休眠或者阻塞,除非当前线程异常,否则其他线程尝试获取锁会一直等待。

 2.lock

  优点:可定时的、可轮询的与可中断的锁获取操作,提供了读写锁、公平锁和非公平锁  

  缺点:需手动释放锁unlock,不适合JVM进行堆栈跟踪

 3.相同点 

  都是可重入锁

字数:1655 发布于 2 个月前
Copyright 2018-2021 Siques