Java多线程高并发进阶篇(二)-synchronized实现原理剖析

在多线程并发编程中,线程安全是关注的重点。一般来说,使用多线程编程是为了获得更高的执行效率,如果连最基本的数据准确性都无法保证,那么谈论并发就没有任何意义。

一.为什么说同步锁(synchronized锁)是重量级锁?

在谈到高并发多线程编程中,估计大家接触到最多的就是synchronized,它在并发编程中是一个元老级别的角色。我们把synchronized锁(同步锁)称为重量级锁,是因为它会导致争用不到锁的线程阻塞,而阻塞或者唤醒一个线程就需要操作系统介入,并且需要在用户态和内核态之间转换,而这个转换过程需要花费大量的资源(因为用户态和内核态都有自己各自的内存空间,在状态切换过程中,用户态需要将参数变量等内容发送给内核态,而内核态需要记录用户态在切换过程的状态)。

二.同步锁的实现原理

在说实现原理之前,我们需要知道同步锁的实现基础:每一个Java对象都可以作为锁!为什么这么说?我们先来点基础内容。

1.根据同步锁的作用范围,可以把同步锁分为:

①对象锁:对给定的对象加锁,在进入代码块之前需要获取此对象的锁。

②实例锁:作用于类中的实例方法,锁是当前实例对象。

③类锁:作用于类中的静态方法,锁是当前类的Class对象。

我们先来一小段代码,看下它的字节码。

public class SynchronizedDemo {
	Object object = new Object();
	public void testObjectLock() {
		synchronized (object) {
				System.out.println("对象锁");
	    }
	}
	
	public synchronized void testInstanceLock() {
		System.out.println("实例锁");
	}
	
	public static synchronized void testClassLock() {
		System.out.println("类锁");
	}
}

我们看一下对象锁在字节码中的表现形式。


 在字节码中,我们发现了一对monitorenter和monitorexit指令。

对了,对象锁的同步实现就是使用monitorenter和monitorexit指令实现的,而实例锁和类锁是使用另外一种方式实现的,细节在JVM规范里并没有详细说明,但是可以明确的是实例锁肯定也可以使用此方式实现。

2.我们在JVM规范中关于这两指令的描述,一探究竟:

JVM规范关于monitorenter 写道 
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.

我们解释下这段话(大家可以借助谷歌翻译),对于monitorenter 解释如下:

每个锁对象都与一个监视器相关联。 当监视器被占用时,就会处于被锁定状态。 当有线程执行monitorenter指令时,会尝试获取与锁对象关联的监视器的所有权,过程如下所示:

①如果与锁对象关联的监视器的计数器值为0,则线程获取监视器的所有权,并将监视器的计数器值设置为1。 

②如果线程已经拥有了与锁对象关联的监视器,那么它将重新进入监视器(也就是重新获取监视器的所有权),然后将监视器的计数器值+1。

③如果另外一个线程已经拥有了与锁对象关联的监视器,则线程会阻塞,直到监视器的计数器值变为0,然后尝试获取所有权。

JVM规范关于monitorexit 写道
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.

对于monitorexit解释如下: 

执行monitorexit指令的线程必须是锁对象对应的监视器的所有者。指令执行时,监视器的计数器数减1,如果减1后进入数为0,那线程退出监视器,不再是这个监视器的所有者。其他被这个监视器阻塞的线程可以尝试去获取这个监视器的所有权。 

从JVM的规范要求的实现描述中,我们可以看到,实现对象锁的方法就是使用monitorenter和monitorexit指令。

这也就解释了一句话:每一个对象都可以作为一个锁对象存在!

下一帖,我们介绍下锁的信息存储在哪,以及锁的升级(也叫膨胀)过程及对比。

猜你喜欢

转载自zhaodengfeng1989.iteye.com/blog/2418458