https://www.imooc.com/video/23597

1. synchronized的作用

能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果。

2. synchronized 的两种用法

2.1 对象锁
  • 包括方法锁(默认锁对象为this当前实例对象)和同步代码块(自己指定锁对象)

    1. public synchronized void add() {
    2. i++;
    3. }
    1. public void add() {
    2. synchronized (this) {
    3. i++;
    4. }
    5. }
    1. Object obj = new Object();
    2. public void add() {
    3. synchronized (obj) {
    4. i++;
    5. }
    6. }

    2.2 类锁
  • 值synchronized修饰静态的方法或指定锁为Class对象

  • Java类可能有很多个对象,但只有一个Class对象
    1. public static synchronized void add() {
    2. i++;
    3. }
    1. public static void add() {
    2. synchronized (Demo2.class){
    3. i++;
    4. }
    5. }
    特别要注意:类锁是Class对象,是唯一的,不同对象间可以形成锁等待。

总结: 对于同一把锁,同一时刻只能被一个线程持有,其他线程要等待锁释放。
注意:线程执行方法抛出异常,会自动释放持有的锁。

3. synchronized的性质

3.1 可重入性
  • 指的是同一线程的外层函数获得锁以后,内层函数可以直接再次获取该锁。
  • 好处:避免死锁,提升封装性

    3.2 不可中断性

    一旦这个锁已经被别的线程获得,如果我还想获得,我只能选择等待或者阻塞,知道别的线程释放这个锁。如果别的线程永远不释放锁,那么我只能永远地等下去。 简单一句话:synchronized会死等,直到得到锁。

    4. synchronized的原理

    4.1 通过字节码查看加锁和释放锁的原理
  • 获取和释放锁的时机:进入和退出绒布代码块(包括抛出异常)

    • 对于ReentryLock,我们要自己控制加锁和释放锁的时机
  • synchronize在字节码中会别转为以下指令

    • monitorenter
    • monitorexit
      4.2 可重入的原理
  • 可重入原理:加锁次数计数器

  • JVM会记录被加锁的次数
  • 第一次加锁时,次数从0变为1,之后如果再次加锁,就从1变为2,以此类推。
  • 退出一层同步代码块时,计数减一,当计数为0的时候,代表锁释放

    4.3 synchronized 可见性
  • synchronized 可以保证可见性。

    5. synchronized的缺陷

  • 效率低:锁的释放情况少,试图获得锁时不能设定超时,不能中断一个正在试图获得锁的线程。

    • 锁的释放情况少,指线程执行完同步代码块或执行过程中抛出了异常才能释放锁。
    • 试图获得锁时不能设定超时,也就是说会一直试图获取锁
    • synchronized
  • 不够灵活(读写锁更灵活):加锁和释放的时机单一,每个锁仅有单一的条件(某个对象),可能不够的。
  • 无法知道是否成功获取到锁

使用注意点:锁的范围不宜过大,避免锁的嵌套(锁的嵌套可能会形成死锁)。