这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战
一、Synchronized的作用
主要是保证多线程环境下的线程安全。
二、Synchronized种类
1. 对象锁
- 包含方法锁(默认锁对象为this当前实力对象),方法锁形式:synchronized修饰普通方法,锁默认对象为this
public class MySynchronized {
public synchronized void m1(){
}
}
上面这种方法可以被:下面这种方式替代:
public void m3(){
synchronized(this){ //synchronized 需要包住m3 所有的内部代码片段
}
}
- 同步代码块锁(自己制定锁对象)代码块形式:手动指定锁对象
public class MySynchronized {
public void m1(){
synchronized (this){
}
}
或者如下
public class MySynchronized {
Object obj= new Object();
public void m1(){
synchronized(obj){
}
}
2. 类锁
指sychronized修饰静态的方法或指锁为Class对象, 概念: java类可能有有很多个对象,但是只有一个class对象 本质: 所以所谓的类锁,不过是Class对象的锁而已
用法和效果:类锁只能在同一时刻被一个对象拥有 形式1:synchronized加载static方法上
public class MySynchronized {
public synchronized static void m1() {
}
}
形式2:synchronized(*.class)代码块
public class MySynchronized {
public void m2() {
synchronized (MySynchronized.class) {
}
}
}
三、常见多线程情况下Synchronized的举例
-
两个线程同时访问一个对象的相同的synchronized方法 串行
-
两个线程同时访问两个对象的相同的synchronized方法 锁对象不同,互不干扰,并行
-
两个线程同时访问两个对象的相同的static的synchronized方法 串行
-
两个线程同时访问同一对象的synchronized方法与非synchronized方法 并行
-
两个线程访问同一对象的不同的synchronized方法 同一对象锁,串行
-
两个线程同时访问同一对象的static的synchronized方法与非static的synchronized方法 锁不同,并行
-
方法抛异常后,会释放锁吗 如果一个线程在进入同步方法后抛出了异常,则另一个线程会立刻进入该同步方法
-
目前进入到被synchronized修饰的方法,这个方法里边调用了非synchronized方法,是线程安全的吗? 线程安全
四、synchronized的性质
1. 可重入性
指的是同一线程的外层函数获取锁之后,内层函数可以直接再次获取该锁
Java典型的可重入锁:synchronized、ReentrantLock 好处:避免死锁,提升封装性 粒度范围证明: 情况一:同一方法是可重入的---递归调用本方法
/**
* 可重入测试----使用递归方式
* 情况1:同一个类中,同一方法可重入
*/
public class SynchronizedTest {
int i = 0;
//主线程可以重入以this为锁对象的method方法
public static void main(String[] args) {
SynchronizedTest s1 = new SynchronizedTest();
s1.method();
}
// 同步方法
private synchronized void method() {
if (i <=3) {
i++;
System.out.println(i);
method();
}
}
}
输出:
1
2
3
4
情况二:可重入不要求是同一个方法
/**
* 可重入测试----
* 情况2:同一个类中,不同方法可重入
*/
public class SynchronizedTest2 {
//主线程可以重入以this为锁对象的method方法
public static void main(String[] args) {
SynchronizedTest2 s1 = new SynchronizedTest2();
s1.method1();
}
// 同步方法
private synchronized void method1() {
System.out.println("method1");
method2();
}
private synchronized void method2() {
System.out.println("method2");
}
}
输出:
method1
method2
情况三:可重入不要求是同一个类中的
public class Demo1 {
public synchronized void method(){
System.out.println("我是Demo1");
}
}
class Demo1Zi extends Demo1{
public synchronized void method(){
System.out.println("我是Demo1儿子");
super.method();
}
public static void main(String[] args) {
Demo1Zi zi = new Demo1Zi();
zi.method();
}
}
输出:
我是Demo1儿子
我是Demo1
2. 不可中断性
一旦这个锁被别的线程获取了,如果我现在想获得,我只能选择等待或者阻塞,直到别的线程释放这个锁,如果别的线程永远不释放锁,那么我只能永远的等待下去。
相比之下,Lock类可以拥有中断的能力,第一点:如果我觉得我等待的时间太长了,有权中断现在已经获取到锁的线程执行;第二点:如果我觉得我等待的时间太长了不想再等了,也可以退出。
五、synchronized的缺点
1、效率低(jdk1.6对其进行了优化)
1)、锁的释放情况少(线程执行完成或者异常情况释放)
2)、试图获得锁时不能设定超时(只能等待)
3)、不能中断一个正在试图获得锁的线程(不能中断)
==jdk1.6对synchronized进行了优化,有一个锁升级的过程,使得效率有一个提升,但是锁升级不可逆。==
2、不够灵活
加锁和释放的时机比较单一,每个锁仅有单一的条件(某个对象),可能是不够的
比如:读写锁更灵活
六、如何选择Lock和synchronized关键字
本着优先避免出错的原则,得出以下结论:
- 如果可以的话,尽量优先使用java.util.concurrent各种类(不需要考虑同步工作,不容易出错)
- 优先使用synchronized,这样可以减少编写代码的量,从而可以减少出错率
- 若用到Lock或Condition独有的特性,才使用Lock或Condition
七. 总结
JVM会自动通过使用monitor来加锁和解锁,保证了同一时刻只有一个线程可以执行指定的代码,从而保证线程安全,同时具有可重入和不可中断的特性。
八. 参考链接
阿里面试:跟我死磕Synchronized底层实现,我满分回答拿了Offer