Java中synchronized基本原理与基本用法


关于synchronized的用法网上的博文实在是不能太多,但是我觉得讲得都不是特别清楚或者太复杂,因为一般情况下都是列举它的中用法,说实话,我记不住,所以查阅了相关资料,从原理上先说明,其次至于它的用法,知道原理了,用法根本不用记,举一反三。

关于monitor

在JVM的规范中,有这么一些话:   
 “在JVM中,每个对象和类在逻辑上都是和一个监视器相关联的”   
 “为了实现监视器的排他性监视能力,JVM为每一个对象和类都关联一个锁” 。

这句话是从别处抄来的,也能说明一些问题。
监视器(monitor),每一个对象和类都有一个monitor,这个monitor内部包含独占锁、入口队列以及等待队列。主要包含这三个部分,可能每个人对它的翻译都不太一样,我个人比较支持这种翻译。至于这三者之间的关系,我打算在后续有关wait()的
博文中详细进行讲述。这篇主要说一下独占锁

独占锁,一个对象的独占锁在同一时刻只能被同一线程所拥有。
这句话其实说得比较明确了,一个线程获取了独占锁,别的线程就不能获取这个独占锁,这是JVM做的事情。

那么我们想实现同步块的话,那岂不是利用独占锁就可以了?同步前强制要求先获取独占锁,然后执行逻辑,最后释放独占锁。
那么在第二个线程在获取不到独占锁的时候,这个线程在干嘛呢?它应该是在入口队列里面,对独占锁进行竞争。

关于synchronized的原理

我们知道synchrnoized一种基本用法如下:

synchronized (对象或类){
	//执行逻辑
}

这边把java代码编译成字节码之后是

monitorenter
//逻辑代码
monitorexit

字面意思就是先进入监控器,进入以后,执行完自己的逻辑以后,然后再从监控器里面出来。

我觉得这个可能和其他博文里面说得比较符合,他们把监控器比作是一个容器,容器里面包含了好几个部分。也就是我上面说的几个部分。

因此我猜测可能是这样,先进入监控器,再进入它的入口队列,去竞争获取独占锁,如果获取成功了,然后就可以继续执行了,但是如果获取不到,那继续一直等待竞争。

如果想看一下上述的字节码文件,可以按照如下命令去查看:

javap -c Test.class

当然,先得编译成class文件,这就不用说了。刚开始我也是在别的博文里面看见的,不过担心别人是在瞎说,所以也亲自实践了一下。

synchronized与独占锁的思考

从上述可以看见,synchronized使用了独占锁的机制,我的几点思考如下:

  1. 独占锁只有一把。
  2. 使用synchronized并不是锁住了某个对象或者类,仅仅只是获得了这个对象的独占锁。
  3. 其余线程使用synchronized,也得获取独占锁。因此做到了同步的访问控制。
  4. 独占锁应用于同步代码块,与非同步代码块没有任何关系。
  5. 获取独占锁只是拥有了当前对象或类的同步代码执行权。

摸清楚了它的基本原理以后,我现在比较反感的一种说法就是:使用synchronized就是锁住了一个对象或者类或者一个方法。但是仔细想想之前很多人这样说,这简直是大错特错。

因为根据我们的常识来说,比如我把门锁了,那么房子里面的一切对外都是不可访问的。显然实际上并不是这个意思,但是问题来了,生活当中应该怎么描述呢?显然上述思考比较啰嗦,好像用:锁住了一个对象最为简单。

synchronized的基本用法

知道了原理,我觉得用法就非常简单了。

我觉得可以按照两个维度来讲解。
第一个维度是:修饰的位置
我觉得两种,一个是方法体上面,一个是代码块,如下:

public synchronized void Test() {
	System.out.println(123);
}
	synchronized (this) {
		System.out.println(123);
	}

第二个维度是:修饰的对象
也是两种,一个是修饰类,一个是修饰对象,如下:
修饰类

	synchronized (SynTest.class) {
		System.out.println(123);
	}
	public static synchronized void Test() {
		System.out.println(123);
}

修饰对象

	synchronized (this) {
		System.out.println(123);
	}
	public synchronized void Test() {
		System.out.println(123);
}

当然修饰对象的时候,不一定this,可以用任何一个实例化的对象。
我觉得从第二个维度来说,更加全面一些,第一种维度只是写法上的区别。
基本判断方法就是:

  1. 修饰类:修饰静态方法,或者代码块synchronized后面跟的是一个类。
  2. 修饰对象:修饰非静态方法,或者代码块synchronized后面跟的是一个对象。

基本结论就是:

  1. 修饰类:其他线程中不能访问这个类的任何一个对象的静态同步代码块区域,不影响非同步块代码的访问。
  2. 修饰对象:其他线程中不能访问这个对象的同步代码块区域,不影响非同步块代码的访问。

synchronized的扩展用法

比如在一个类中,出现了两个同步代码块,但是这两者之间并没有并发问题,所以就不需要两处都去获取当前对象的独占锁,因为这样做两个代码块会互斥,影响效率。这个时候可以用一个全局变量来完成互斥,如下:

private String lock=new String();
	public void Test() {
		synchronized (lock) {
			System.out.println(123);
		}
	}

这个也可以保证当前代码块是线程安全的。而且不与其余同步块互斥。

猜你喜欢

转载自blog.csdn.net/ywg_1994/article/details/88284539