10.关于synchronized的一切,我都写在这里了

简介: 大家好,我是王有志。我们已经完成了synchronized的学习,今天我们利用学习到的知识去回答一些关热点问题。

大家好,我是王有志,欢迎和我聊技术,聊漂泊在外的生活。快来加入我们的Java提桶跑路群:共同富裕的Java人

之前我们已经通过3篇文章由浅到深的分析了synchronized的用法和原理:

还有一篇是关于并发控制中常用锁的设计一文看懂并发编程中的锁。可以说是从设计,到用法,再到实现原理,对synchronized进行了全方位的剖析。今天我们就用之前学习的内容解答一些热点题目。

Tips:标题是“抄袭”《一年一度喜剧大赛》作品《梦幻丽莎发廊》的台词。由仁科,茂涛,蒋龙,蒋诗萌和欧剑宇表演,爆笑推荐。

synchronized基础篇

基础篇的问题主要集中在synchronized的用法上。例如:

  1. synchronized锁.class对象,代表着什么?

  2. synchronized什么情况下是对象锁?什么情况下是类锁?

  3. 如果对象的多个方法添加了synchronized,那么对象有几把锁?

很多小伙伴解答这类问题时喜欢背诸如“synchronized修饰静态方法,作用的范围是整个静态方法,作用对象是这个类的所有对象”这种,相当于直接背结论,忽略了原理。

先来回顾下synchronized都问啥?中提到的原理:Java中每个对象都与一个监视器关联。synchronized锁定与对象关联的监视器(可以理解为锁定对象本身),锁定成功后才可以继续执行

举个例子:

public class Human {
  public static synchronized void run() {
    // 业务逻辑
  }
}

synchronized修饰静态方法,而静态方法是类所有,可以理解为synchronized锁定了Human.class对象,接下来我们推导现象。

假设线程t1执行run方法且尚未结束,即t1锁定了Human.class,且尚未释放,那么此时所有试图锁定Human.class的线程都会被阻塞。

例如,线程t2执行run方法会被阻塞:

Thread t2 = new Thread(Human::run);
t2.start();

如果我们添加如下方法呢?

public synchronized void eat() {
  // 业务逻辑  
}

synchronized修饰实例方法,属于对象所有,可以理解为synchronized锁定了当前对象。执行以下测试代码,会发生阻塞吗?

new Thread(Human::run, "t1")).start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
  Human human = new Human();
  human.eat();  
}, "t2")).start();

答案是不会,因为t1锁定的是Human.class对象,而t2锁定的是Human的实例对象,它们之间不存在任何竞争。

再添加一个方法,并执行如下测试,会发生阻塞吗?

public static synchronized void walk() {
  // 业务逻辑
}  

public static void main(String[] args) throws InterruptedException {
  new Thread(Human::run, "t1").start();
  TimeUnit.SECONDS.sleep(1);
  new Thread(Human::walk, "t2").start();  
}

答案是线程t2会阻塞,因为线程t1和线程t2在竞争同一个Human.class对象,而很明显线程t1会抢先锁定Human.class对象。

最后再做一个测试,添加如下方法和测试代码:

public synchronized void drink() {
  // 业务逻辑
}

public static void main(String[] args) throws InterruptedException {
  Human human = new Human();
  new Thread(human::eat, "t1").start();
  TimeUnit.SECONDS.sleep(1);
  new Thread(human::drink, "t2").start();
  new Thread(()-> {
    Human t3 = new Human();
    t3.eat();
  }, "t3").start();

  TimeUnit.SECONDS.sleep(1);
  new Thread(()-> {
    Human t4 = new Human();
    t4.eat();
  }, "t4").start();
}

小伙伴们可以按照用法结合原理的方式,推导这段代码的运行结果。

Tips:业务逻辑可以执行TimeUnit.SECONDS.sleep(60)模拟长期持有。

synchronized进阶篇

进阶篇则主要考察synchronized的原理,例如:

  • synchronized是如何保证原子性,有序性和可见性的?

  • 详细描述synchronized的原理和锁升级的过程。

  • 为什么说synchronized是悲观锁/非公平锁/可重入锁?

synchronized的并发保证

假设有如下代码:

private static int count = 0;
public static synchronized void add() {
  ......
  count++;
  ......
}

在正确同步的前提下,同一时间有且仅有一个线程能够执行add方法,对count进行修改。

此时便“营造”了一种单线程环境,而编译器对重排序做出了“as-if-serial”的保证,因此不会存在有序性问题。同样的,仅有一个线程执行count++,那么也不存在原子性问题

至于可见性,我们在什么是synchronized的重量级锁中释放重量级锁的部分看到了storeload内存屏障,该屏障保证了写操作的数据对下一读操作可见。

Tips

  • synchronized并没有禁止重排序,而是“营造”了单线程环境;

  • 内存屏障我们在volatile中重点解释。

synchronized的实现原理

synchronized是JVM根据管程的设计思想实现的互斥锁。synchronized修饰代码块时,编译后会添加monitorenter和monitorexit指令,修饰方法时,会添加ACC_SYNCHRONIZED访问标识。

Java 1.6之后,synchronized的内部结构实际上分为偏向锁,轻量级锁和重量级锁3部分。

当线程进入synchronized方法后,且未发生竞争,会修改对象头中偏向的线程ID,此时synchronized处于偏向锁状态。

当产生轻微竞争后(常见于线程交替执行),会升级(膨胀)到轻量级锁的状态。

当产生激烈竞争后,轻量级锁会升级(膨胀)到重量级锁,此时只有一个线程可以获取到对象的监视器,其余线程会被park(暂停)且进入等待队列,等待唤醒。

synchronized的特性实现

为什么说synchronized是悲观锁?来回顾下一文看懂并发编程中的锁中提到的悲观锁,悲观锁认为并发访问共享总是会发生修改,因此在进入临界区前一定会执行加锁操作

那么对于synchronized来说,无论是偏向锁,轻量级锁还是重量级锁,使用synchronized总是会发生加锁,因此是悲观锁。

为什么说synchronized是非公平锁?接着回顾下非公平锁,非公平性体现在发生阻塞后的唤醒并不是按照先来后到的顺序进行的

在synchronized中,默认策略是将cxq队列中的数据移入到EntryList后再进行唤醒,并没有按照先后顺序执行。实际上我们也不知道cxq和EntryList中的线程到底谁先进入等待的。

为什么说synchronized是可重入锁?回顾下可重入锁,可重入指的是允许同一个线程反复多次加锁

使用上,synchronized允许同一个线程多次进入。底层实现上,synchronized内部维护了计数器_recursions,发生重入时,计数器+1,退出时计数器-1。

通过_recursions的命名,我们也能知道Java中的可重入锁就是POSIX中的递归锁。

结语

本文的内容比较简单,主要是根据之前的内容回答一些热点问题。不说是做到学以致用,至少做到学习后,能回答一些面试问题。

当然更深层次的意义,在于指导我们合理的使用synchronized以及我们可以从中借鉴到的设计思想。


好了,今天就到这里了,Bye~~

目录
相关文章
|
8月前
总结 synchronized
总结 synchronized
31 0
|
4月前
|
存储 安全 Java
|
6月前
|
安全 算法 Java
synchronized 同步锁
Java中的synchronized关键字用于实现线程同步,可以修饰方法或代码块。 1. 修饰方法:当一个方法被synchronized修饰时,只有获得该方法的锁的线程才能执行该方法。其他线程需要等待锁的释放才能执行该方法。 2. 修饰代码块:当某个对象被synchronized修饰时,任何线程在执行该对象中被synchronized修饰的代码块时,必须先获得该对象的锁。其他线程需要等待锁的释放才能执行同步代码块。Java中的每个对象都有一个内置锁,当一个对象被synchronized修饰时,它的内置锁就起作用了。只有获得该锁的线程才能访问被synchronized修饰的代码段。使用synch
38 0
|
6月前
ReentrantLock和Synchronized简单比较
ReentrantLock和Synchronized简单比较
20 0
|
7月前
Synchronized
作用:能够保证在同一时刻最多有一个线程执行该段代码,以保证并发的安全性。(当第一个线程去执行该段代码的时候就拿到锁,并独占这把锁,当方法执行结束或者一定条件后它才释放这把锁,在没释放锁之前,所有的线程处于等待状态)
32 0
|
9月前
|
Java
synchronized的总结
synchronized的总结
synchronized的总结
|
9月前
|
前端开发 Java Spring
方法上加上 synchronized 就可以了么
方法上加上 synchronized 就可以了么
|
9月前
|
存储 缓存 安全
synchronized的简单理解
synchronized的简单理解
66 0
|
10月前
|
Java
07.synchronized都问啥?
大家好,我是王有志。经过JMM和锁的铺垫,今天我们正式进入synchronized的内容,来看看关于synchronized面试中都会问啥?
36 1
07.synchronized都问啥?
|
存储 缓存 安全
【Synchronized】
【Synchronized】
104 0
【Synchronized】