synchronized实现原理(简单易懂)

synchronized实现原理以及锁升级

文章目录

    • synchronized实现原理以及锁升级
    • synchronized
    • synchronized实现原理

synchronized

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步代码块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
  3. 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
  4. 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。
    synchronized实现原理(简单易懂)_第1张图片

synchronized实现原理

对象锁(monitor)机制

先来看一段简单的代码:

public class Test{
	private static Object object = new Object();
	public static void main(String[] args) {
		synchronized (object) {
			System.out.println("hello world");
		}
	}
}

下面我们使用javap反编译后看看生成的部分字节码:
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 // Field object:Ljava/lang/Object;
3: dup
4: astore_1
5: monitorenter // 瞪大眼睛看这里!!!
6: getstatic #3 // Field
java/lang/System.out:Ljava/io/PrintStream;
9: ldc #4 // String hello world
11: invokevirtual #5 // Method
java/io/PrintStream.println:(Ljava/lang/String;)V
14: aload_1
15: monitorexit // 瞪大眼睛看这里!!!
16: goto 24
19: astore_2
20: aload_1
21: monitorexit // 瞪大眼睛看这里!!!
22: aload_2
23: athrow
24: return

执行同步代码块后首先要先执行monitorenter指令,退出的时候monitorexit指令。通过分析之后可以看出,使用Synchronized进行同步,其关键就是必须要对对象的监视器monitor进行获取,当线程获取monitor后才能继续往下执行,否则就只能等待。而这个获取的过程是互斥的,即同一时刻只有一个线程能够获取到monitor。是否注意到上述字节码中包含一个monitorenter指令以及多个monitorexit指令。这是因为Java虚拟机需要确保所获得的锁在正常执行路径,以及异常执行路径上都能够被解锁。

public synchronized void foo();
	descriptor: ()V
	flags: ACC_PUBLIC, ACC_SYNCHRONIZED // look!!!!!
	Code:
	stack=2, locals=1, args_size=1
	0: getstatic #5 // Field
	java/lang/System.out:Ljava/io/PrintStream;
	3: ldc #6 // String hello world
	5: invokevirtual #7 // Method
	java/io/PrintStream.println:(Ljava/lang/String;)V
	8: return
	LineNumberTable:
	line 9: 0
	line 10: 8
}

monitorenter 和 monitorexit 操作所对应的锁对象是隐式的。对于实例方法来说,这两个操作对应的锁对象是 this;对于静态方法来说,这两个操作对应的锁对象则是所在类的 Class 实例。关于 monitorenter 和monitorexit 的作用,我们可以抽象地理解为每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针
当执行 monitorenter 时,如果目标锁对象的计数器为 0,那么说明它没有被其他线程所持有。在这个情况下,Java 虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加 1。在目标锁对象的计数器不为 0 的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加 1,否则需要等待,直至持有线程释放该锁。当执行 monitorexit 时,Java 虚拟机则需将锁对象的计数器减 1。当计数器减为 0 时,那便代表该锁已经被释放掉了。之所以采用这种计数器的方式,是为了允许同一个线程重复获取同一把锁。

举个例子,如果一个 Java类中拥有多个 synchronized 方法,那么这些方法之间的相互调用,不管是直接的还是间接的,都会涉及对同一把锁的重复加锁操作。因此,我们需要设计这么一个可重入的特性,来避免编程里的隐式约束。

你可能感兴趣的:(synchronized实现原理(简单易懂))