synchronized关键字与Lock锁原理

发布时间 2023-06-14 20:55:39作者: _mcj

1.synchronized

synchronized关键字是JVM提供的内置锁,是通过Monitor两种来实现的,分别是当其作用在类上和方法上时。
类上:

  • 测试代码
public class Test {

    public void test() {
        synchronized (this) {
            System.out.println(1);
        }
    }

}
  • 测试类的反编译结果,执行javap -v Test.class
.....
public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: dup
         2: astore_1
         3: monitorenter
         4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         7: iconst_1
         8: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
        11: aload_1
        12: monitorexit
        13: goto          21
        16: astore_2
        17: aload_1
        line 10: 4
        line 11: 11
        line 12: 21
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      22     0  this

  • 分析
    从上面的反编译class文件可以看出,synchronized关键字作用在类上时,其是通过monitor锁来实现的,通过monitorenter指令来表示资源已被锁定,monitorexit指令来表示资源已被释放。
    另外,资源在被锁定的时候mointor会将monitor的计数设置为1,由于synchronized是可重入锁,所以如果是同一个线程再次持有该锁时,则会将monitor的计数加1。如果是不同的线程则会阻塞。而资源在被释放的时候则会将计数进行减1,当计数为0时,则证明该资源已被释放,可以被其他线程获取。

方法上:

  • 测试代码
public class Test {

    public synchronized void test() {
        System.out.println(1);
    }

}
  • 测试类的反编译结果,执行javap -v Test.class
......
 public synchronized void test();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: iconst_1
         4: invokevirtual #3                  // Method java/io/PrintStream.println:(I)V
         7: return
      LineNumberTable:
        line 9: 0
        line 10: 7
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0  this

  • 分析
    通过上面的反编译结果可以看出当synchronized关键字作用在方法上时其是通过标志ACC_SYNCHRONIZED来进行表示的,当方法上有该标志时,则说明当前方法是由synchronized关键字进行修饰的,需要进行相应的加锁操作,是隐式的。

2.Lock

lock是java中的一个类。其主要实现是通过五个方法lock(),lockInterruptibly(),trylock(),
trylock(time:long, unit:TimeUnit),unlock()

  • lock()
    阻塞模式抢占锁,如果当前线程抢占锁成功,那么其继续向下执行程序的业务逻辑,否则,当前线程会阻塞,直到持有锁的线程释放锁后再继续抢占锁。
  • lockInterruptibly()
    可中断模式抢占锁的方法。当前线程在抢占锁的过程中,能够响应中断信号,从而能够中断当前线程。
  • trylock()
    非阻塞模式下抢占锁的方法。抢占锁时,线程不会阻塞,而会立即返回抢占锁的结果。
  • trylock(time:long, unit:TimeUnit)
    在trylock()的基础上,加了抢占锁的时间限制
  • unlock()
    释放锁

关于lock的源码解析,可以看AQS与CAS分析这篇博客。

3.总结

类别 synchronized lock
存在层次 Java的关键字,属于jvm层面 java中的一个类
是否需要手动释放 不需要手动释放,jvm会自动帮忙释放 需要在final块中手动进行释放
锁的获取 只有阻塞模式抢占锁一种,一个线程获取之后,另一个线程需要阻塞 有多种模式,可阻塞,可非阻塞
锁状态 无法判断 可以判断
锁类型 可重入,不可中断,非公平 可重入,可中断,可公平也可非公平
性能 少量同步 大量同步