带你深入理解和解剖 synchronized

1. 是什么?

synchronized 是 JAVA 关键字,用在方法或代码块。控制方法或代码同一时间只有一个线程执行,用来解决多线程同时访问出现的并发问题。

2. 使用分类(作用域)

对象锁(代码块)

  • 表现形式1:加在方法上
private synchronized boolean sellTicketsWithSyncMethod(int count) {
    if (totalTickets - count < 0) {
        return false;
    } else {
        totalTickets = totalTickets - count;
        return true;
    }
}
  • 表现形式2:加在对象上

锁多个对象时,相互不干扰

private final Object LOCK = new Object();
private boolean sellTicketsWithSyncObject(int count) {
    synchronized (LOCK) {
        if (totalTickets - count < 0) {
            return false;
        } else {
            totalTickets = totalTickets - count;
            return true;
        }
    }
}
  • 表现形式3:this当前对象上
private boolean sellTicketsWithSyncObject(int count) {
    synchronized (this) {
        if (totalTickets - count < 0) {
            return false;
        } else {
            totalTickets = totalTickets - count;
            return true;
        }
    }
}

类锁

  • 表现形式1:static
public static synchronized void staticMethod() {
   // todo
}
  • 表现形式2:synchronized(xxx.class)
public Class SyncClass {
    private void method () {
        synchronized (SyncClass.class) {
            //todo
        }
    }
}

对象锁和类锁区别?

对象锁锁的是一个对象。不同对象下互不干扰。而类只有一个。

3. synchronized 的特征

  1. 它是一个可重入锁。
  2. JAVA 语言的关键字,由虚拟机字节码指令实现。(下面深入讲解)
    1. 用 monitor 来控制进出
    2. moniterenter 进入
    3. Monitorexit 为退出
  3. 不可中断,无法控制锁的超时时间,可能会造成阻塞。而 ReentranLock 可以,这个后面讲解。
  4. synchronized 只有非公平锁。 公平锁和非公平锁的区别
  5. 粒度是对象,锁的是对象而非变量。

3.1 可重入讲解

指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。从而避免死锁。

演示:

public class SynchronizedReentrantTest {
    int i = 0;
    public synchronized void method1() {
        method2();
    }
 
    private synchronized void method2() {
    }
 
    private synchronized void reentrantSelf() {
        if (i == 0) {
            i++;
            reentrantSelf();
        }
    }
 
    public static void main(String[] args) {
        SynchronizedReentrantTest instance = new SynchronizedReentrantTest();
        
        // 重入其他方法
        instance.method1();
        
        // 自己重入自己
        instance.reentrantSelf();
        
        // 当然也可以重入其他类方法,这里不演示了。
    }
}

4. 深入理解 synchronized (反编译来看看)

如何反编译?

  1. 编译 javac xxxx.java
  2. 反编译 javap -c -p -v xxxx.class

以下是截取的部分源码:

public class SynchronizedMethodDif {
    //...

    // 作用于对象上
    private static final Object LOCK = new Object();
    private static boolean sellTicketsWithSyncObject(int count) {
        synchronized (LOCK) {
            if (totalTickets - count < 0) {
                return false;
            } else {
                totalTickets = totalTickets - count;
                return true;
            }
        }
    }
    
    // 作用于 static 方法上
    private static synchronized void soutStatic() {
        System.out.println(Thread.currentThread().getName() + " is sleeping ");
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " : 1");
        System.out.println(Thread.currentThread().getName() + " finish sleeping ");
    }
    
    // 作用于普通方法上
    private synchronized void soutNonStatic() {
        System.out.println(Thread.currentThread().getName() + " is sleeping ");
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " : 1");
        System.out.println(Thread.currentThread().getName() + " finish sleeping ");
    }
    
    //...
}

反编译后:

关注 monitorenter、monitorexit、ACC_SYNCHRONIZED 这三个指令。

// 作用于对象上
private static boolean sellTicketsWithSyncObject(int);
descriptor: (I)Z
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
Code:
  stack=2, locals=3, args_size=1
    0: getstatic     #10                 // Field LOCK:Ljava/lang/Object;
    3: dup
    4: astore_1
    5: monitorenter
    ...
    16: monitorexit
    17: ireturn
    ...
    27: aload_1
    28: monitorexit
    29: ireturn
    ...
    31: aload_1
    32: monitorexit
    33: aload_2
    34: athrow
    ...
    frame_type = 75 /* same_locals_1_stack_item */
      stack = [ class java/lang/Throwable ]

...
// 作用于 static 上
private static synchronized void soutStatic();
    descriptor: ()V
    flags: (0x002a) ACC_PRIVATE, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=0
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    ...
    frame_type = 4 /* same */

...
// 作用于 普通方法 上
private synchronized void soutNonStatic();
    descriptor: ()V
    flags: (0x0022) ACC_PRIVATE, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=2, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
    ...
    frame_type = 4 /* same */

4.1 原理分析

-> 作用于 代码块(对象) 上:

通过 monitor 来控制,使用monitorenter 和 monitorexit 指令控制线程进出。

monitor 讲解

是一个监视器,执行是依赖于对象的锁计数

  1. monitorenter:每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
    1. 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者
    2. 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1
    3. 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权
  2. monitorexit:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。

-> 作用于 方法

  • 方法使用 ACC_SYNCHRONIZED 标识
  • static 方法,则锁是作用在类上
  • 非 static 方法,则锁作用在类对象上

喜欢就点个赞呗~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Charlven

你的鼓励将是我最大的动力哦

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值