简介
本文介绍synchronized
关键字的两个重要的性质可重入性和不可中断性,我们将用代码来实现和验证这两个性质。
明白这种理论性质有什么用?
明白了特性你才能懂得Synchronized
的作用范围,使用的时候才不容易犯错。
一.可重入性
可重入性:同一个线程的外层函数获得锁后,内存函数可以直接获取该锁。
举个生活中的可重入性的例子:
当我们排队的时候,经常遇到有个别不文明的人,他们会让自己的好友直接插队在他的位置,而且有时还一次插队好几个。这就是一个人获得了优先权后,会让投靠他的所有人都获得该权利,这就是一种可重入性的体现,所谓一人得道,鸡犬升天。
1.Synchronized可重入性的两个优点:
- 避免死锁
避免死锁的原因: 如果synchronized
不具备可重入性,当一个线程想去访问另一个方法时,它自身已经持有一把锁,而且还没有释放锁,又想获取另一个方法的锁,于是造成了永远等待的僵局,就会造成死锁。有了可重入性后,自己持有一把锁,并且可以直接进入到内层函数中,就避免了死锁。
- 提升封装性
编程人员不需要手动加锁和解锁,统一由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
的作用范围和锁的等待机制,如果这些概念在我们心中都是模糊的、不确定的,我们在使用的时候,也往往用的不放心,因为多线程中的问题有时候达不到一定的并发量,即便有错误也是发现不了的,可能你本地起多个线程测试,也没发现问题,一上线就出错,所以我们对这些最常用的并发工具的执行机制和原理,一定要门儿清,才能在编码时,就避免了很多问题的出现。喜欢本文,请收藏和点赞。