在之前的文章中,已经发布了常见的面试题,这里我花了点时间整理了一下对应的解答,由于个人能力有限,不一定完全到位,如果有解答的不合理的地方还请指点,在此谢过。

本文主要描述的是java多线程中面试的锁机制。这个是面试中的重点内容,也是平时开发过程中经常碰到的问题,所以我们一定要重点了解锁的内容。锁的内容主要有关键字volatile,synchronized,这两个要掌握其实现的原理,下面的内容会重点描述这两个关键字的实现原理以及面试中可能出现的问题。如果对这些感兴趣的话,可以看下公众号中源码的内容。

了解volatile关键字么?作用是什么?能否保证线程安全?

volatile是java并发的基石,其两个作用是有序性和可见性的保证。但是不能保证原子性,也不能保证线程安全。volatile在实现可见性上主要是通过lock指令实现。如果我们对一个变量加上volatile关键字,那么在编译成汇编的时候会加上lock前缀,该指令的作用是:

  1. 将当前处理器的缓存行数据写回到系统内存
  2. 这个写回内存的操作会使其他cpu里面缓存的了该内存地址的变为无效,

这两个和我们之前讲的内存模型里面是一致的,就是实现写回主存,其他内存地址无效,那么其他地址如果要读取数据的话,就必须要从主存中从新拉取数据。通过该指令实现可见性。

在实现有序性的时候,volatile在实现上是通过限制编译器重排序,指令集重排序实现的。volatile规定了重排序的规则:

从这个表格上我们可以看出以下几点:

  1. 第二个操作是volatile写时,第一个操作无论是啥都不能重排序,这个操作保证写前和写后的不会顺序错乱,写前的不会在写后操作。
  2. 第一个操作是volatile读时,第二个操作无论是啥都不能重排序。这个保证volatile读的顺序,读后的不会到读前面。
  3. 第一个操作是volatile写,第二个操作是volatile读时,不能重排序。这个保证volatile写在读之前。

Java编译器在实现上面规则的时候,会使用内存屏障指令来实现。具体的实现规则如下:

  1. 在每个volatile写操作的前面插入一个StoreStore屏障,后面插入一个StoreLoad屏障,用来完成对写的保护。
  2. 在每个volatile读操作的后面插入一个LoadLoad屏障和LoadStore屏障,用来完成对读操作的保护。

编译器虽然增加了内存屏障指令,但是有些cpu产商会把这些屏障再进一步优化,不过这个优化实际上是能够保证先后顺序的。通过这种方式实现有序性。

了解synchronized关键字么?什么是锁升级?Synchronized的三种使用方式?如果synchronized锁住的静态方法和非静态方法的区别是什么?

自java6之后,synchronized关键字被优化,其性能基本持平Reentrylock。在优化后的synchronized有了锁升级的理念,其主要表现在:无锁-》偏向锁-》轻量级锁-》重量级锁。加锁的过程会随着线程竞争的激烈程度而不断加深,直至重量级锁,一旦锁升级之后,就不会降级。如果了解过锁原理的话就会知道加锁的过程实际就是获取某个对象的过程,一旦有某个线程优先获取到该对象,其他线程就只能等待,所以锁一定是锁住某个对象。Synchronized也是类似,其偏向锁和轻量级锁的实现是通过修改对象头内容实现的,重量级锁则是通过对象监视器实现的。稍微说下,一个java对象头中包含markword,class metadata address ,array length等内容,synchronized修改的是markword字段。下面具体说下几种锁的实现原理:

  1. 偏向锁的原理:在加锁的过程中,HotSpot的研究者发现绝大部分的时候是不发生竞争的,换句话说就是只有一个线程想要这个锁,并且占用到这个锁的线程往往是同一个,那就意味着我们只要记录下加锁的线程是不是这个线程就可以了,如果是这个线程,就直接可以占用到锁。这样我们都不用使用CAS操作就能保证锁住的是同一个线程。Synchronized的偏向锁的原理就在于此,在上面的锁mark word字段中偏向锁是记录了占用锁的id,这样方便每次判断是不是同一个线程进行锁的占用。偏向锁在占用的时候,使用比较简单,但是如果当另外一个线程也来占锁的话,那这个时候,就必须开始锁撤销,偏向锁的撤销过程相对复杂,它需要在一个全局安全点(这个需要了解jvm,在垃圾回收的时候,也需要进入到安全点)开始进行:暂停持锁线程,判断该线程是否还存活,如果不存活的话,就直接置为无锁,如果还存活,那就需要遍历栈中的锁记录,如果该线程不需要锁,那么就置为无锁或者偏向其他线程,如果该线程还需要锁,那么就需要产生锁竞争,实际上这个时候偏向锁已经不合适了,会被标记为不合适,最后唤醒线程。
  2. 轻量级锁的原理:线程会先申请一个栈用来保存锁记录空间,将对象头中的锁mark word信息复制到栈中,然后CAS将锁markword信息设置为锁记录指针。如果设置成功,就占锁成功,如果设置失败,就自旋。撤销的过程也很简单,CAS替换回原来的mark word信息。
  3. 重量级锁的原理:在java中,每个对象都有与之对应的一个monitor对象,重量级锁采用对象监视器实现,通过monitorentry和monitorexit实现,其实如下:如果对象的monitor进入数是0的话,则直接持有,并且设置owner为当前线程;如果线程占用了monitor对象,则可以重复进入,并且将monitor数值加1;如果有其他线程占用,那么当前线程会被阻塞,直到monitor数值为0 的时候唤醒开始重试占用,这个和AQS基本实现类似了。

synchronized关键字的常见使用方式:锁住代码块,锁住静态方法,锁住非静态方法。具体的三种方式如下:

Object lock = new Object();//synchronized后面加一个对象
public void testLock() {
synchronized(lock){  //锁住代码块
}
}
public static synchronized void testLock(){//Synchronized 放在一个静态方法里
}   public synchronized void testLockA(){//Synchronized放在一个普通方法里
}  

在这三种方法中,代码块的使用方式锁住的是lock对象(也就是synchronized括号里面的对象),静态方法锁住的类对象,普通方法锁住的是持有这个方法的实例对象。从这里我们知道静态方法和非静态方法锁住的是不同的对象,所以这两者是可以有两个线程同时访问的,换句话说,静态方法和非静态方法不构成锁竞争。

写一个线程安全的单例?

其实单例有很多种实现方式,如果感兴趣的话,可以看下公众号里面的并发系列文章中《聊聊单例的实现》,里面包含了绝大部分单例的实现方式。在这里主要是说下synchronized和volatile实现的方式。

public class Singleton {  private static volatile Singleton instance ;//使用volatile  private Singleton(){}  public static Singleton getInstance(){  if (null == instance){  synchronized (Singleton.class){//使用双重检查  if (null == instance){  instance = new Singleton();  }  }  }  return instance;  }
}  

在这里有两点需要说明的:(面试的时候也要重点提到)

  1. 为什么需要双重检查?

假设一下,如果是单次检查的话,有两种情况:

第一种:直接开始检查

 public static Singleton getInstance(){  synchronized (Singleton.class){ if (null == instance){  instance = new Singleton();  }  }   return instance;  }  

这个显然性能要慢很多,每个线程过来先加锁,即使是已经初始化完成的也要加锁,显然不可取,性能要差很多。

第二种:检查后加锁

public static Singleton getInstance(){  if (null == instance){  synchronized (Singleton.class){  instance = new Singleton();  }  }  return instance;  }  

这种方法会造成instance不是单例,假设两个线程检查到instance都是null的话,其中一个先加锁成功,初始化instance,之后,释放锁,另一个线程再次加锁初始化instance。这样就造成了instance不是单例。显然也不满足要求

  1. 为什么instance要加volatile?不加会有什么问题?

加volatile主要是保证不会指令重排序的问题。不加的话有极小的概率可能会获取到一个instance是不可用的。我们看下为什么会发生这个情况,重点分析一下getInstance函数。

public static Singleton getInstance(){  if (null == instance){  //1synchronized (Singleton.class){//2  if (null == instance){  //3instance = new Singleton(); //4 }  }  }  return instance;  }  

问题的根源在于上面的第4行代码,new一个对象。了解java类加载机制的同学可能会知道,新建一个类对象有几个步骤,申请空间,赋值等等。我们将第4行代码简化成下面几个伪代码。

 mem = memallocate//申请内存空间 5Initobject(mem)//初始化对象 6instance = mem //赋值 7

有些编译器将6和7进行指令重排序导致7在6之前,这样就有可能导致一个线程在执行代码7,另一个线程执行1发现instance!= null,立马将instance拿去用,但是这个时候的instance还没有执行6,也就是初始化没完成,instance是一个没有初始化的对象,可能就会导致问题的发生。如果加上volatile之后,就不允许指令6和7互换,从而避免这种问题的发生。

volatile和synchronized是面试的重点内容,请务必重视。其和可重入锁一起构成了java锁的核心,如果在面试中能够将这些内容描述正确,那基本上锁这块的内容就可以了。

本文的内容就这么多,如果你觉得对你的学习和面试有些帮助,帮忙点个赞或者转发一下哈,谢谢。

想要了解更多java内容(包含大厂面试题和题解)可以关注公众号,也可以在公众号留言,帮忙内推阿里、腾讯等互联网大厂哈

Java多线程篇--并发关键字synchronized和volatile相关推荐

  1. 面试题汇总二 Java 多线程篇

    前言 题目汇总来源 史上最全各类面试题汇总,没有之一,不接受反驳 面试题汇总一 Java 语言基础篇 面试题汇总二 Java 多线程篇 面试题汇总三 Java 集合篇 面试题汇总四 JVM 篇 面试题 ...

  2. java多线程与并发_漫画 | Java多线程与并发(一)

    1.什么是线程? 2.线程和进程有什么区别? 3.如何在Java中实现线程? 4.Java关键字volatile与synchronized作用与区别? volatile修饰的变量不保留拷贝,直接访问主 ...

  3. JAVA多线程和并发基础面试问答(转载)

    JAVA多线程和并发基础面试问答 原文链接:http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-ans ...

  4. java多线程与并发原理

    三.java多线程与并发原理 1.进程和线程的区别: 进程和线程的由来: (1)串行:初期的计算机只能串行执行任务,并且需要长时间等待用户输入: (2)批处理:预先将用户的指令集集中成清单,批量串行处 ...

  5. Java多线程与并发相关 — 原理

    Java多线程与并发相关 - 原理 一 synchronized同步 1. 线程安全问题的主要诱因? 存在共享资源(也称临界资源); 存在多条线程共同操作这些共享数据; 2. 解决办法. 同一时刻有且 ...

  6. Java 多线程与并发编程专题

    Java 线程基础 Java 多线程开发 线程安全与同步 并发控制 非阻塞套接字(NIO) Java 5 中的并发 JDK 7 中的 Fork/Join 模式 相关书评 Java 平台提供了一套广泛而 ...

  7. JAVA Java多线程与并发库

    Java多线程与并发库 同步方式 import javax.xml.stream.events.StartDocument;public class TestSynchronized {public ...

  8. JAVA多线程和并发面试问题

    转载自   JAVA多线程和并发面试问题 Java多线程面试问题 1.进程和线程之间有什么不同? 一个进程是一个独立(self contained)的运行环境,它可以被看作一个程序或者一个应用.而线程 ...

  9. Java多线程与并发系列从0到1全部合集,强烈建议收藏!

    在过去的时间中,我写过Java多线程与并发的整个系列. 为了方便大家的阅读,也为了让知识更系统化,这里我单独把Java多线程与并发的整个系列一并罗列于此,希望对有用的人有用,也希望能帮助到更多的人. ...

  10. java多线程:线程同步synchronized(不同步的问题、队列与锁),死锁的产生和解决

    0.不同步的问题 并发的线程不安全问题: 多个线程同时操作同一个对象,如果控制不好,就会产生问题,叫做线程不安全. 我们来看三个比较经典的案例来说明线程不安全的问题. 0.1 订票问题 例如前面说过的 ...

最新文章

  1. 阿里巴巴是如何招人的,如何招到合适的人?
  2. python零基础实例-零基础学习Python开发练习100题实例(1)
  3. C#使用sqlite-net搭建简易的ORM
  4. @RequestParam注解详解
  5. kodi 更改服务器文件,云服务器安装kodi
  6. asp.net 调用本地php,.NET_Asp.net获取服务器指定文件夹目录文件并提供下载的方法,本文实例讲述了Asp.net获取服务 - phpStudy...
  7. 数据结构之求二叉树的所有叶子和以及叶子总数
  8. Linux学习总结(27)——CentOS7及以上系统的systemctl命令使用介绍
  9. 【渝粤教育】电大中专跨境电子商务理论与实务 (3)作业 题库
  10. 科学计算机统计模式中中间数据输错怎么办,科学计算器在统计功能中如何清除以前不需要的数据?...
  11. linux档案内容怎么写,Linux cat输出档案命令详解
  12. 共享店铺系统如何设计?具体如何做?
  13. JavaScript基础知识之DOM
  14. vue-video-player,springboot实现视频分段下载播放
  15. Hash Exercise 21.11.27
  16. ES聚合之Bucket聚合语法讲解
  17. Unity 3D视频播放器场景C#脚本
  18. java se 05
  19. table表格头出现表头错乱
  20. 黑马pink老师前端从入门到精通教程汇总(附源码+配套资料)

热门文章

  1. 修真院java_【修真院JAVA小課堂】JMeter的簡單介紹
  2. 1-2 用Python爬取猫眼票房网上的电影票房信息
  3. MacBook上不显示外接硬盘未装载解决方法
  4. 深入理解Android之Java Security第一部分
  5. Game boy模拟器(3):GPU的时序
  6. 猫哥教你写爬虫 019--debug-作业
  7. 关闭Win10锁屏的防暴力破解功能
  8. 人在囧途之tar命令
  9. ICD建模问题总结(IED Capability Description)
  10. ffmpeg视频剪辑视频长度问题,视频无声音,视频卡顿问题