在技术论坛中,经常看到一种言论:面试造火箭,干活拧螺丝。我们平时写的大部分代码的确是CRDU,再提一个层次,也无非就是揉进去复杂一些的业务逻辑,把一堆的CRDU组合起来。

那么问题来了:我们提倡的研究“底层技术”,难道仅仅是为了面试?或是为了平时码农们聊天时装大佬吗?

当然不是!

小端随着工作年限的增加,深有感悟:

技术是我们程序员的工具箱。

CRDU是我们的默认工具。

平时的点滴积累就是在不断的丰富自己的工具箱,增加工具种类。

而深挖技术细节,就是在更深入的掌握每一个工具的特性。

在工作中遇到问题时,如果一直使用默认工具,那么随着问题域的越来越大,总会遇到捉襟见肘的尴尬。

如果工具箱中不断的加入得心应手的工具,嗯,办法就会总比问题多。

以下面即将讲的synchronized锁为例子,如果对它没有清晰的了解,那么在解决线程安全性问题时,第一反应是尽量避免使用;如果实在避免不掉而用之,也是简单的模仿,这样自己其实心里也是没底的,也不清楚带来的开销有多大影响,甚至不清楚是否能真正解决问题,只能期盼上线后,一切平安...

小端会写一个系列,以面试中的问题作为切入点(毕竟这种问题涉及到的,是大部分技术人员比较关注,平时也经常使用到的技术),深入技术底层进行分析,搭建自己的知识体系。期望对看到这篇文章的您,有所启发。

好,下面进入正题。

如果有人让你讲讲synchronized的实现细节,那么,

恭喜你,这是一个能体现技术深度的好机会!

面试官:synchronized关键字用过吗?讲讲你对他的了解...

有没有一种被他虐我千百遍的感觉?这个知识点也算是面试中的必现题型了。

不过,以小端的经验,有的面试官浅尝辄止,有的面试官则穷追猛打。前一种,可能面试官自己也不太熟悉,我们辛辛苦苦准备的东西,刚开个头,就被叫停了,不尽兴有没有;而后一种,一般会不断的深挖,一直问到我们的知识盲区。

  1.在第一类面试官面前,我们需要引导,需要争取足够的时间把他的知识盲区讲清楚,借此展示出自己的知识深度。

  2.在第二类面试官面前,大部分时间是知识体系对等的交流,这就需要我们做到回答时能提纲掣领,一旦深入细节有条理。

那么问题来了,如何做到呢?

小端利用十一假期,重新梳理和总结相关知识点,试图勾勒出一个5层金字塔结构(后面称为S金字塔),来帮助大家构建自有的完整知识体系。

希望在“大场面”中,你也能做到:任你风起云涌,我自岿然不动!

先放出S金字塔,一睹为快:

      

抛出大纲很重要

我们大多数人,更习惯于平铺直叙的方式,描述一个相对复杂的知识结构。

殊不知对于你的听众来说,其实这是一种负担。

对方要全程集中注意力,要在听后面内容的时候,不断的重复回忆前面听到的内容,只有这样,才能在听到最后时,构建出相对完整的思维模型,才能方便理解对话的真正含义。

否则就会产生压迫感,对自己不熟悉的知识更甚【上述第一种面试官】。

这是人的天性使然。

如果我们在对话的刚开始,就先抛出一个简明的大纲,先帮助对方建立起完整的模型,然后我们再针对每一个分支,做专项描述,让对方只需要边听边对照大纲印证,减少了中间记忆和回忆的环节,无疑会大大减轻对方的压力。

所以,针对这个问题,小端一般会首先告诉对方:我将从关键字的应用(初入山门)、字节码层面的细节(入室弟子)、内部组件(大师兄)、以及组件工作的流程(长老)四个方面来做解释。

PS:可能有的同学要问了,怎么少了一层,S塔尖呢?恩...正如字面,我们只是芸芸众生,这个高度需要潜心研究,还是留给大神以及有志于成为大神的同学吧。

第一层:我们看到的样子-synchronized关键字的应用

这层是基础。

在多线程编程时,synchronized经常被用来解决互斥导致的线程安全性问题。用法有两种个,一种用在方法声明中:

public synchronized void run() {
//...
}

另一种用在方法体代码块中:

 public void increaseCount() {
//……………………
synchronized (this) {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ":" + count++); } }

如果修饰的是静态方法,锁对象是其所在的类对象;如果修饰的是实例方法,锁对象是当前的实例对象。

第二层:机器看到的样子-编译成字节码的细节

Java代码编译为字节码指令后,方法声明中的synchronized对应生成ACC_SYNCHRONIZED关键字。

 public synchronized void doSth();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: ...
3:...
5: return

方法体中的synchronized会对应生成monitorenter,monitorexit。

 public void doSth1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: ldc #5
2: dup
3: astore_1
4: monitorenter
5: getstatic #2
8: ldc #3
10: invokevirtual #4
13: aload_1
14: monitorexit
15: goto 23
18: astore_2
19: aload_1
20: monitorexit
21: aload_2
22: athrow
23: return

当执行到monitorenter关键字时,会申请同步锁;执行到monitorexit关键字时,会释放同步锁。

这里需要注意,monitorexit有两个的作用可以理解为,try...catch...finally,保证在正常执行流程和其他非正常流程时,都能释放锁。

第三层:真实的组成:偏向锁+轻量级锁+重量级锁

在传统重量级锁模型中,加锁解锁是很消耗系统资源的操作。因为加锁解锁操作,涉及到线程的阻塞和唤醒,而阻塞唤醒,是依靠操作系统来实现的,也就需要程序从用户态切换到内核态。

在这种情况下,Java虚拟机做出了最直接的优化-自适应自旋。在加锁失败、以及被唤醒后未获取到锁的时候,进入自旋,以期能在一定的时间内,其他线程释放锁进而加锁成功。这是用CPU的消耗来尽可能避免阻塞唤醒操作的初级解决方案。

这里隐含着一个前提:加锁是最终目的。所以做的任何优化,只是在加锁这件事上,尽量提效。那么怎么来优化呢?

不知你有没有思考过这种问题:我们在写代码的时候是无法确定这段代码是否一定存在线程安全问题的,那么我们采取一切从严标准写,也就是明确标识加锁。然后让虚拟机在执行时,根据运行时的状态来决定加锁不加锁,岂不是完美?

是的!虚拟机已经做到了:在无线程竞争的场景,或者多线程近乎于交替执行的场景,是不需要加锁的(传统的重量级锁)。

这也就印证了一种说法,synchronized锁性能不好,后来经过优化后,性能得到了极大的提升。本质是,在jdk1.6版本中,引入了偏向锁,轻量级锁、重量级锁三层模型.

优化后,虚拟机加锁的策略,可以简单描述成:

如果只有一个线程调用同步代码,显然没有必要加锁。可以通过偏向锁,只需要一次CAS操作。如果重入,都是一些值比较操作,性能消耗极低。

如果多线程近乎于交替执行同步代码,仅需要在每次加锁解锁时,做CAS修改(其实CAS的主要目的是发现竞争)。

如果的确存在多线程竞争情况,再升级为依赖重量级锁来保障。

第四层:组件间的关系-协同

那么上述组件是如何协同工作的呢?

可以说很复杂!复杂到小端不得不用独立一篇来做详细介绍。

【从刷面试题到构建知识体系】Java底层-synchronized锁-1的更多相关文章

  1. 【从刷面试题到构建知识体系】Java底层-synchronized锁-2偏向锁篇

    上一篇通过构建金字塔结构,来从不同的角度,由浅入深的对synchronized关键字做了介绍, 快速跳转:https://www.cnblogs.com/xyang/p/11631866.html 本 ...

  2. 通过面试题学习零散知识:Java面试题整理

     一.如何看待面试题 对于喜欢学习的开发者来说,我们抛开工作和生活的时间,剩余的时间并不多,如果都用于学习的话,也不可能学的下所有感兴趣的技术点,精力也跟不上,我是深感如是.而面试题一般都是零碎的知识 ...

  3. Java知识体系

    Java知识体系 java知识结构.jpg web框架.jpg 计算机课程体系.png 2016-08-19_090929.png 流行的哈希算法生存状况.jpg "JAVA之父" ...

  4. 最强最全的Java后端知识体系

    目录 最全的Java后端知识体系 Java基础 算法和数据结构 Spring相关 数据库相关 方法论 工具清单 文档 @(最强最全的Java后端知识体系) 最全的Java后端知识体系 最全的Java后 ...

  5. github上最全的资源教程-前端涉及的所有知识体系

    前面分享了前端入门资源汇总,今天分享下前端所有的知识体系. 个人站长对个人综合素质要求还是比较高的,要想打造多拉斯自媒体网站,不花点心血是很难成功的,学习前端是必不可少的一个环节, 当然你不一定要成为 ...

  6. github上最全的资源教程-前端涉及的所有知识体系【转】

    github上最全的资源教程-前端涉及的所有知识体系[转自:蓝猫的博客] 综合类 综合类 地址 前端知识体系 http://www.cnblogs.com/sb19871023/p/3894452.h ...

  7. Atitit 知识图谱解决方案:提供完整知识体系架构的搜索与知识结果overview

    Atitit 知识图谱解决方案:提供完整知识体系架构的搜索与知识结果overview   知识图谱的表示和在搜索中的展1 提升Google搜索效果3 1.找到最想要的信息.3 2.提供最全面的摘要.4 ...

  8. android知识体系

    1.Android架构分为4层*应用程序层 Android会同一系列核心应用程序包一起发布,该应用程序包包括email客户端,SMS短消息程序,日历,地图,浏览器,联系人管理程序等.所有的应用程序都是 ...

  9. unity3d所要知道的基础知识体系大纲,可以对照着学习,不定期更新

    本文献给,想踏入3D游戏客户端开发的初学者. 毕业2年,去年开始9月开始转作手机游戏开发,从那时开始到现在一共面的游戏公司12家,其中知名的包括搜狐畅游.掌趣科技.蓝港在线.玩蟹科技.天神互动.乐元素 ...

随机推荐

  1. ZOJ 3872 Beauty of Array 连续子序列求和

    Edward has an array A with N integers. He defines the beauty of an array as the summation of all dis ...

  2. Linux基础_网站权限规划

    Linux系统默认的权限: 对于文件来说, 默认的权限: rw-r--r-- 644 对于目录来说:rwxr-xr-x  755 网站比较安全的权限: 网址程序存放在/app/blog 目录下面. 1 ...

  3. Go语言标准库之log

    无论是软件开发的调试阶段还是软件上线之后的运行阶段,日志一直都是非常重要的一个环节,我们也应该养成在程序中记录日志的好习惯. log Go语言内置的log包实现了简单的日志服务.本文介绍了标准库log ...

  4. 下一个排列(Leetcode31)解读

    本题代码来自Leetcode官方,个人对其理解后,生成自己的注解,以便更好的让读者理解.如有侵权,立即删除! public class Main31 { public static void main ...

  5. android 滚动时间选择器

    一.概述 滚动时间选择现在貌似很常用,所以就总结一下,显示效果一般般 , 做个参考吧! 以上就是效果图,可以滚动选择 日期时间, 由于是在 5.0系统运行的,貌似5.0系统做了什么变动,下面的 &qu ...

  6. 学习笔记_第十天_方法_方法的综合练习---ref练习

    using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.T ...

  7. 二分练习题3 查找小于x的最大元素 题解

    题目描述 现在告诉你一个长度为 \(n\) 的有序数组 \(a_1, a_2, ..., a_n\) ,以及 \(q\) 次询问,每次询问会给你一个数 \(x\) ,对于每次询问,你需要输出数组 \( ...

  8. 原生JavaScript时间倒计时的方法

    这个思路是来源用%的方法来做的: 以前用%做过转秒的 现在用来做倒计时方法: 需要用到的方法是getTime:获取距离1970年1月1日午夜00:00之间的毫秒差: var targetTime=ne ...

  9. STL中nth_element的用法

    nth_element函数原型有四个,详细我就不一一累赘了,我们就用最普通的用法寻找第k位置的元素. 函数用法为:nth_element(first,kth,end). first,last 第一个和 ...

  10. java 线程监控

    线程的五种状态 * 新建:new * 运行:runnable * 等待:waitting(无限期等待),timed waitting(限期等待) * 阻塞:blocked * 结束:terminate ...