继续面试大纲系列文章。

  从这一篇开始,我们进入ava编程中的一个重要领域---多线程!多线程就像武学中对的吸星大法,理解透了用好了可以得道成仙,俯瞰芸芸众生;而滥用则会遭其反噬。

  在多线程编程中要渡的首个“劫”,则是Synchronized。了解其底层实现,无论是在面试中还是在平时工作中,都大有裨益。我们知其然,知其所以然,才能得心应手少挖坑。

  我们知道,多线程的核心思想是通过增加线程数量来并发的运行,来提高效率,也就是数量决胜论,而不是质量决胜(提高每个线程的处理能力)。多线程编程中面临的最大挑战,是如何解决多个线程同时修改一个公用的变量所带来的变量值不确定性问题。顺着这个思路分析,常用办法,无非就是,要么对变量动手,在一个线程修改时,变量值被锁定。要么是对修改的操作动手,在该段代码执行时,对其加锁,其他线程不可以在同一时刻进入该段代码执行。

  Synchronized,正是实现了后一种办法。

Synchronized

  1. 问:你平时涉及到多线程编程多不多?谈谈你对Synchronized锁的理解
  2. 分析:多从实现原理,工作机制来描述
  3. 答:
    1.   在多线程编程中,为了达到线程安全的目的,我们往往通过加锁的方式来实现。而Synchronized正是java提供给我们的非常重要的锁之一。它属于jvm级别加锁,底层实现是:在编译过程中,在指令级别加入一些标识来实现的。例如,同步代码块,会在同步代码块首尾加入monitorenter和monitorexit字节码指令,这两个指令都需要一个reference类型的参数,指明要加锁和解锁的对象,同步方法则是通过在修饰符上加acc_synchronized标识实现。在执行到这些指令(标识)时,本质都是获取、占有、释放monitor锁对象实现互斥,也就是说,同一时刻,只能有一个线程能成功的获取到这个锁对象。我们看一段加了synchronized关键字的代码编译后的字节码。编译前:

      1 public class test {
      2 public test() {
      3 }
      4 public static void main(String[] args) {
      5 synchronized(new Object()){
      6 int i = 0;
      7 }
      8 }
      9 }

      编译后:

      public class test extends java.lang.Object{
      public test();
      Code:
      0: aload_0
      1: invokespecial #1; //Method java/lang/Object."":()V
      4: nop
      5: return public static void main(java.lang.String[]);
      Code:
      0: new #2; //class Object
      3: dup
      4: invokespecial #1; //Method java/lang/Object."":()V
      7: dup
      8: astore_1
      9: monitorenter // Enter the monitor associated with object
      10: iconst_0
      11: istore_2
      12: nop
      13: aload_1
      14: monitorexit // Exit the monitor associated with object
      15: goto 23
      18: astore_3
      19: aload_1
      20: monitorexit // Be sure to exit monitor...
      21: aload_3
      22: athrow
      23: nop
      24: return
      Exception table:
      from to target type
      10 15 18 any
      18 21 18 any }

      重点关注14行,和20行。

    2.  在使用Synchronized时,用到的方法是wait和notify(或notifyAll),他们的原理是,调用wait方法后,线程让出cpu资源,释放锁,进入waiting状态,进入等待队列【第一个队列】。当有其他线程调用了notify或者notifyAll唤醒时,会将等待队列里的线程对象,移入阻塞队列【第二个队列】,状态是blocked,等待锁被释放后(这个释放时机,由虚拟机来决定,人为无法干预),开始竞争锁。
    3. Synchronized无法中断正在阻塞队列或者等待队列的线程。

  4.扩展:Synchronized提供了以下几种类型的锁:偏向锁、轻量级锁、重量级锁。在大部分情况下,并不存在多线程竞争,更常见的是一个线程多次获取同一个锁。那么很多的消耗,其实是在锁的获取与释放上。Synchronized一直在被优化,可以说Synchronized虽然推出的较早,但是效率并不比后来推出的Lock差。

  1.  偏向锁:在jdk1.6中引入,目的是消除在无竞争情况下的同步原语(翻译成人话就是,即使加了synchronized关键字,但是在没有竞争的时候,没必要去做获取-持有-释放锁对象的操作,提高程序运行性能)。怎么做呢?当锁对象第一次被线程A获取时,虚拟机会把对象头中的标志位设置为01,也就是代表偏向模式。同时把代表这个线程A的ID,通过CAS方式,更新到锁对象头的MarkWord中。相同的线程下次再次申请锁的时候,只需要简单的比较线程ID即可。以上操作成功,则成功进入到同步代码块。如果此时有其他线程B来竞争该锁,分两种情况做不同的处理:
    1. 如果线程A已执行完(并不会主动的修改锁对象的状态),会直接撤销锁对象的状态为未锁定,标志位为00;
    2. 如果线程A还在持有该锁,则锁升级为轻量级锁。  
  2. 轻量级锁:也是JDK1.6中引入的,轻量级,是相对于使用互斥量的重量级锁来说的。线程发生竞争锁的时候,不会直接进入阻塞状态,而是先尝试做CAS修改操作,进入自旋,这个过程避免了线程状态切换的开销,不过要消耗cpu资源。详细过程是:
    1.   线程尝试进入同步代码块,如果锁对象未被锁定,在当前线程对应的栈帧中,建立锁记录的空间,用于存储锁对象Mark Word的拷贝。
    2. 然后JVM用CAS方式尝试将锁对象的MarkWord内容替换为指向前述“锁记录”的指针。如果成功,当前线程则持有了锁,处于轻量级锁定状态;如果失败,会首先检查当前MarkWord是否已经指向当前线程栈帧的“锁记录”,如果是,就说明当前线程已经拥有了这个锁,直接重入即可。否则就表明是其他线程持有锁,那么进入自旋(其实就是重试CAS修改操作)。
    3. 释放锁时,是使用CAS来讲MarkWord和“锁记录”里的内容互换。如果成功,成功释放;如果事变,表明当前锁存在竞争(被其他线程修改了MarkWord里的数据),此时,锁会升级为重量级锁。
  3. 重量级锁:也就是我们使用的互斥量方式实现的锁,当存在多线程竞争时,只要没拿到锁,就会进入阻塞状态,主要消耗是在阻塞-唤起-阻塞-唤起的线程状态切换上。
  4. 上面介绍的三种类型的锁,是JVM来负责管理使用哪种类型锁,以及锁的升级(注意,没有降级)。
  5. 这里涉及到锁升级,对象头MarkWord等内容,如果详细说,可能又是一大篇文章了,如果大家感兴趣,可以另起一篇。
  6. synchronized就先介绍到这里,下一篇预告:ReentrantLock相关
  7. 如果你看的爽,请点击右下角的“推荐”,是对小端坚持分享原创的最大鼓励。也可以关注小端的个人公众号 :   pnxsxb  ,会分享更多的原创技术文章。

欢迎扫描以下二维码:

BAT美团滴滴java面试大纲(带答案版)之三:多线程synchronized的更多相关文章

  1. BAT美团滴滴java面试大纲(带答案版)之三:多线程Lock

    继续面试大纲系列文章. 这是多线程的第二篇. 多线程就像武学中对的吸星大法,理解透了用好了可以得道成仙,俯瞰芸芸众生:而滥用则会遭其反噬. 在多线程编程中要渡的第二个“劫”,则是Lock.在很多时候, ...

  2. 金三银四跳槽季,BAT美团滴滴java面试大纲(带答案版)之一:Java基础篇

    Java基础篇: 题记:本系列文章,会尽量模拟面试现场对话情景, 用口语而非书面语 ,采用问答形式来展现.另外每一个问题都附上“延伸”,这部分内容是帮助小伙伴们更深的理解一些底层细节的补充,在面试中可 ...

  3. BAT美团滴滴java面试大纲(带答案版)之四:多线程Lock

    每天学习一点点 编程PDF电子书.视频教程免费下载:http://www.shitanlife.com/code 这是多线程的第二篇. 多线程就像武学中对的吸星大法,理解透了用好了可以得道成仙,俯瞰芸 ...

  4. Java面试大纲-java面试该做哪些准备,java开发达到这样的水平可以涨工资

    Java培训结束,面临的就是毕业找工作.在找工作时,就要针对性地做充分的面试准备.准备不充分的面试,完全是浪费时间,更是对自己的不负责. 上海尚学堂Java培训整理出Java面试大纲,其中大部分都是面 ...

  5. 2019滴滴java面试总结 (包含面试题解析)

    2019滴滴java面试总结  (包含面试题) 本人6年开发经验.今年年初找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条.滴滴等公司offer,岗位是既有php也有Java后端开发,最终选择去了滴滴 ...

  6. Java面试宝典(2018版)

    置顶 2018年11月10日 23:49:18 我要取一个响亮的昵称 阅读数:8893    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/chen ...

  7. Java面试:投行的15个多线程和并发面试题

    多线程和并发问题已成为各种 Java 面试中必不可少的一部分.如果你准备参加投行的 Java 开发岗位面试,比如巴克莱银行(Barclays).花旗银行(Citibank).摩根史坦利投资公司(Mor ...

  8. Java面试:投行的15个多线程和并发面试题(转)

    多线程和并发问题已成为各种 Java 面试中必不可少的一部分.如果你准备参加投行的 Java 开发岗位面试,比如巴克莱银行(Barclays).花旗银行(Citibank).摩根史坦利投资公司(Mor ...

  9. 2019 滴滴java面试笔试总结 (含面试题解析)

       本人5年开发经验.18年年底开始跑路找工作,在互联网寒冬下成功拿到阿里巴巴.今日头条.滴滴等公司offer,岗位是Java后端开发,因为发展原因最终选择去了滴滴,入职一年时间了,也成为了面试官, ...

随机推荐

  1. ORACLE等待事件:SQL*Net message from client & SQL*Net message to client

    在ORACLE当中有两个很常见的等待事件"SQL*Net message from client"与"SQL*Net message to client",两者 ...

  2. linux alias 用法

    转自linux alias http://www.maomao365.com/?p=2597 : linux中命令别名设置: <span style="color:blue;font- ...

  3. java应用系统运行速度慢的解决方法

    场景:我们在部署了TOMCAT应用,刚刚开始启动的一个段时间内.访问系统的速度比较快.但是过了一段时间,应用系统就慢慢的变慢起来了.服务的访问加载时间慢慢变长. 问题解决思路: 1,查看部署应用系统的 ...

  4. Django 2.1.3 文档

    https://blog.csdn.net/lengfengyuyu/article/details/83342553#3_23

  5. NPOI 笔记

    前言 文档:http://npoi.codeplex.com/documentation 示例:https://npoi.svn.codeplex.com/svn/ 下载:https://www.nu ...

  6. 【Git学习一】Git 初始化

    在开始Git之旅之前,我们需要设置一下Git的配置变量. 1.告诉Git当前用户的姓名和邮件地址,配置用户名和邮件地址将在版本库提交时用到. 例子: ------------------------- ...

  7. LeetCode算法题-Intersection of Two Linked Lists(Java实现)

    这是悦乐书的第178次更新,第180篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第37题(顺位题号是160).编写程序以找到两个单链表交叉的节点.例如: 以下两个链表: ...

  8. 使用selenium时,使用从系统启动浏览器与通过自动化驱动方式启动浏览器控件ID不一样解决方法

    最近遇到一个怪事,通过正常打开浏览器,按照正常的web登录然后点击进入系统流程,将各控件的ID识别成功,然后使用 python3+selenium写好脚本,高高兴兴的用脚本跑时老是提示找不到控件,然后 ...

  9. Python 简易版选课系统

    一.创建学生类 # # 创建学生类 import random class Student: def __init__(self,num,name,address,course_lst=None): ...

  10. C#字节数组与字符串转换

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; usin ...