Java中Synchronized的可重入性和不可中断性的分析和代码验证

简介

本文介绍synchronized关键字的两个重要的性质可重入性和不可中断性,我们将用代码来实现和验证这两个性质。

明白这种理论性质有什么用?

明白了特性你才能懂得Synchronized的作用范围,使用的时候才不容易犯错。

一.可重入性

可重入性:同一个线程的外层函数获得锁后,内存函数可以直接获取该锁。

举个生活中的可重入性的例子:

当我们排队的时候,经常遇到有个别不文明的人,他们会让自己的好友直接插队在他的位置,而且有时还一次插队好几个。这就是一个人获得了优先权后,会让投靠他的所有人都获得该权利,这就是一种可重入性的体现,所谓一人得道,鸡犬升天。

1.Synchronized可重入性的两个优点:

  1. 避免死锁

避免死锁的原因: 如果synchronized不具备可重入性,当一个线程想去访问另一个方法时,它自身已经持有一把锁,而且还没有释放锁,又想获取另一个方法的锁,于是造成了永远等待的僵局,就会造成死锁。有了可重入性后,自己持有一把锁,并且可以直接进入到内层函数中,就避免了死锁。

  1. 提升封装性

编程人员不需要手动加锁和解锁,统一由JVM管理,提高了可利用性。

2.Synchronized可重入性的粒度(作用范围)

Synchronized可重入性的作用范围是整个获得锁的线程,线程内部的所有被调用的方法都共享该锁。

线程内部的哪些方法共享同一把锁?

  • 同一个类中的同一个方法
  • 同一个类中的不同的方法
  • 不同类中的方法

3.代码验证上述三种情况的可重入性

①同一个类中的同一个方法,验证可重入性:

实验目的:通过递归反复调用同一个方法,若被synchronized修饰的方法,可以递归调用自己的方法,则表示在同步方法中具有可重入性。

public class RecursiveLockCondition1 {

	//跳出递归的条件,需在递归方法外部声明,否则会造成死循环
	int i = 0;

	private synchronized void method() {
		System.out.println("递归调用:i=" + i);
		if (i == 0) {
			i++;
			//递归调用本方法
			method();
		}
	}


	public static void main(String[] args) {
		RecursiveLockCondition1 recursiveLockCondition1 = new RecursiveLockCondition1();
		recursiveLockCondition1.method();
	}
}

执行结果:

递归调用:i=0
递归调用:i=1

②同一个类中的不同的方法,验证可重入性:

现象:从一个同步方法中,调用同一个类中的另一个同步方法是可以被执行的。证明:

同一个类中,一个同步方法调用另一个同步方法,可重入性质依然存在。
public class RecursiveLockCondition2 {

	private synchronized void method1() {
		System.out.println("method1执行");
		method2();
	}

	private synchronized void method2() {
		System.out.println("method2执行");
	}

	public static void main(String[] args) {
		RecursiveLockCondition2 condition2 = new RecursiveLockCondition2();
		condition2.method1();
	}
}

预期和实际结果:method2()方法被执行。

method1执行
method2执行

③不同类中的方法,验证可重入性:

使用子类调用父类中的同步方法,证明:
调用不同类中的同步方法也是可重入的。

public class RecursiveLockCondition3 {

	protected synchronized void method() {
		System.out.println("父类方法,被执行");
	}

}

// 子类
class ChildClass extends RecursiveLockCondition3 {

	@Override
	protected synchronized void method() {
		System.out.println("子类方法,被执行");
		super.method();
	}

	public static void main(String[] args) {
		ChildClass childClass = new ChildClass();
		childClass.method();
	}
}

预期和实际结果:父类的method()方法也被执行。

子类方法,被执行
父类方法,被执行

二.不可中断性

定义:

当锁被别的线程获得以后,如当前线程想获得,只能等待或堵塞,直到其他线程释放了这个锁。如果其他线程不释放,当前线程会一直等待下去。

代码证明:synchronized是不会自动中断的。即synchronized具有不可中断性。

代码模拟:一个获得锁线程如果没有执行完毕,另一个线程会一直等待下去。

public class UninterruptedCondition implements Runnable {

	static UninterruptedCondition instance = new UninterruptedCondition();

	@Override
	public void run() {
		method();
	}

	private synchronized void method() {
		//死循环:一直打印当前线程名
		while (true) {
			System.out.println("线程名:" + Thread.currentThread().getName() + ",正在执行");
		}
	}

	/**
	 * 
	 * 
	 */
	public static void main(String[] args) {
		Thread thread1 = new Thread(instance);
		Thread thread2 = new Thread(instance);
		thread1.start();
		thread2.start();
		while (thread1.isAlive() || thread2.isAlive()) {

		}
		System.out.println("执行结束");
	}
}

预期和实际结果:只有一个线程在打印,另一个线程会一直等待下去。

线程名:Thread-1,正在执行
线程名:Thread-1,正在执行
线程名:Thread-1,正在执行
线程名:Thread-1,正在执行
线程名:Thread-1,正在执行
输出相同,省略...

总结

本文从代码层面分析和证明了synchronized关键字的可重入性和不可中断性,通过这些分析,我们主要想理解synchronized的作用范围和锁的等待机制,如果这些概念在我们心中都是模糊的、不确定的,我们在使用的时候,也往往用的不放心,因为多线程中的问题有时候达不到一定的并发量,即便有错误也是发现不了的,可能你本地起多个线程测试,也没发现问题,一上线就出错,所以我们对这些最常用的并发工具的执行机制和原理,一定要门儿清,才能在编码时,就避免了很多问题的出现。喜欢本文,请收藏和点赞。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章