大家好,我是王有志。关注王有志,一起聊技术,聊游戏,从北漂生活谈到国际风云。

之前我们已经通过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修饰代码块时,编译后会添加monitorentermonitorexit指令,修饰方法时,会添加ACC_SYNCHRONIZED访问标识。

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

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

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

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

synchronized的特性实现

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

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

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

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

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

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

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

结语

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

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


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

10.关于synchronized的一切,我都写在这里了的更多相关文章

  1. 人人都写过的5个Bug!

    大家好,我是良许. 计算机专业的小伙伴,在学校期间一定学过 C 语言.它是众多高级语言的鼻祖,深入学习这门语言会对计算机原理.操作系统.内存管理等等底层相关的知识会有更深入的了解,所以我在直播的时候, ...

  2. jQuery之父:每天都写点代码

    去年秋天,我的“兼职编程项目”遇到了一些问题:要不是从 Khan Academy 的项目里挪出时间来的话,我根本没办法将不理想的进度弥补上. 这些项目遇到了一些严重的问题.之前的工作我主要是在周末,有 ...

  3. Method Swizzing中一般替换方法都写在Category类别里吗?有没有别的实现方式

    Method Swizzing中一般替换方法都写在Category类别里吗?有没有别的实现方式 Method Swizzing中一般替换方法都写在Category类别里吗?有没有别的实现方式 > ...

  4. php重建二叉树(函数缺省参数相关的都写在后面,比如array_slice函数中的$length属性,故第一个参数是操作的数组)

    php重建二叉树(函数缺省参数相关的都写在后面,比如array_slice函数中的$length属性,故第一个参数是操作的数组) 一.总结 牛客网和洛谷一样,是真的好用 二.php重建二叉树 输入某二 ...

  5. ? 原创: 铲子哥 搜狗测试 今天 shell编程的时候,往往不会把所有功能都写在一个脚本中,这样不太好维护,需要多个脚本文件协同工作。那么问题来了,在一个脚本中怎么调用其他的脚本呢?有三种方式,分别是fork、source和exec。 1. fork 即通过sh 脚本名进行执行脚本的方式。下面通过一个简单的例子来讲解下它的特性。 创建father.sh,内容如下: #!/bin/bas

    ? 原创: 铲子哥 搜狗测试 今天 shell编程的时候,往往不会把所有功能都写在一个脚本中,这样不太好维护,需要多个脚本文件协同工作.那么问题来了,在一个脚本中怎么调用其他的脚本呢?有三种方式,分别 ...

  6. Delphi/C#之父首次访华:55岁了 每天都写代码

    Delphi.C#之父Anders Hejlsberg 近日首次访华,并在10月24日和27日参加了两场见面会,分享了他目前领导开发的TypeScript项目,并与国内前端开发者近距离交流.本文就为读 ...

  7. java 多线程10:synchronized锁机制 之 锁定类静态方法 和锁定类.Class 和 数据String的常量池特性

    同步静态方法 synchronized还可以应用在静态方法上,如果这么写,则代表的是对当前.java文件对应的Class类加锁.看一下例子,注意一下printC()并不是一个静态方法: public ...

  8. 面向切面编程AOP——加锁、cache、logging、trace、同步等这些较通用的操作,如果都写一个类,则每个用到这些功能的类使用多继承非常难看,AOP就是解决这个问题的,python AOP就是装饰器

    面向切面编程(AOP)是一种编程思想,与OOP并不矛盾,只是它们的关注点相同.面向对象的目的在于抽象和管理,而面向切面的目的在于解耦和复用. 举两个大家都接触过的AOP的例子: 1)java中myba ...

  9. Java开发最常犯的10个错误,打死都不要犯!

    原文:http://www.programcreek.com/2014/05/top-10-mistakes-java-developers-make/ 译文:cnblogs.com/chenpi/p ...

  10. Python制作AI贪吃蛇,很多很多细节、思路都写下来了!

    前提:本文实现AI贪吃蛇自行对战,加上人机对战,读者可再次基础上自行添加电脑VS电脑和玩家VS玩家(其实把人机对战写完,这2个都没什么了,思路都一样) 实现效果: 很多人学习python,不知道从何学 ...

随机推荐

  1. 齐博x1注意事项:再强调严禁用记事本改任何文件

    提醒大家,X1任何文件,不要用记事本修改.比如这个用户就改出问题了 导致后台不能升级. 当然这是问题之一, 还有其它意料之外的问题.还没发现. 这个用户做一个测试风格. 配置文件可能是用记事本修改的. ...

  2. 13.内建函数eval()

      eval函数 eval()函数十分强大 -- 将字符串当成有效的表达式来求值并返回计算结果 例如下图,eval会将字符串的引号去掉并且计算返回结果  

  3. 五、kubernetes节点与令牌管理

    Kubernetes节点与令牌管理 一.令牌管理 查看令牌 [root@master ~]# kubeadm token list 删除令牌 [root@master ~]# kubeadm toke ...

  4. ML-L1、L2 正则化

    出现过拟合时,使用正则化可以将模型的拟合程度降低一点点,使曲线变得缓和. L1正则化(LASSO) 正则项是所有参数的绝对值的和.正则化不包含theta0,因为他只是偏置,而不影响曲线的摆动幅度. \ ...

  5. 自学 TypeScript 第二天 编译选项

    前言: 昨天我们学习了 TS 的数据类型,不知道大家回去以后练习没练习,如果你练习了一定会发现一个问题,我们的 TS 好像和 JS 不太一样 JS 写完之后直接就可以放到页面上,就可以用了,而我们的 ...

  6. 【云原生 · Kubernetes】部署高可用 kube-controller-manager 集群

    个人名片: 因为云计算成为了监控工程师‍ 个人博客:念舒_C.ying CSDN主页️:念舒_C.ying 部署高可用 kube-controller-manager 集群 12.1 创建 kube- ...

  7. Go | 函数注意事项

    细节汇总 函数的形参列表可以是多个,返回值列表也可以是多个 形参列表和返回值列表的数据类型,可以是值类型.也可以是引用类型 函数的命名遵循标识符命名规范,首字母不能是数字,首字母大写表示该函数可以被本 ...

  8. Axios 类似于for循环发送批量请求{:axios.all axios.spread}。

    Axios的请求都是异步的!不能用for循环遍历去批量发送请求 那如果我们需要类似与这样的请求怎么办呢 for(let i =0;i<array.length;i++){ axios.post( ...

  9. MySQL遇到的坑:sql_mode=only_full_group_by不兼容

    描述: 解决方案: show variables like "%sql_mode%"; SET sql_mode=(SELECT REPLACE(@@sql_mode," ...

  10. 第2-4-9章 规则引擎Drools实战(2)-信用卡申请

    目录 9.2 信用卡申请 9.2.1 计算规则 9.2.2 实现步骤 9.2 信用卡申请 全套代码及资料全部完整提供,点此处下载 本小节我们需要通过Drools规则引擎来根据规则进行申请人的合法性检查 ...