> 文档中心 > Java并发编程核心知识点之连环问

Java并发编程核心知识点之连环问

一、平时业务代码里面使用过多线程吗,能举例几个多线程的业务场景吗?

 二、能举几个不是线程安全的数据结构吗?

三、在Java中可以有哪些方法来保证线程安全

四、了解volatile关键字不?能否解释下,然后这和synchronized有什么大的区别 

五、为什么会出现脏读? 

 六、你说volatile可以避免指令重排,能否解释下什么是指令重排

七、知道 happens-before吗,能否简单解释下? 


导读:本博文从多线程的业务场景引入到线程安全的数据结构问题,再到volatile关键字 以及和synchroized的区别 ,和脏读的出现原因 指令重排 先行发生原则一步步连环追问~

一、平时业务代码里面使用过多线程吗,能举例几个多线程的业务场景吗?

异步任务:用户注册、记录日志
定时任务:定期备份日志、备份数据库
分布式计算:Hadoop处理任务mapreduce,master-wark(单机单进程)
服务器编程:Socket网络编程,一个连接一个线程

二、能举几个不是线程安全的数据结构吗?

HashMap、ArrayList、LinkedList

三、在Java中可以有哪些方法来保证线程安全

加锁,比如synchronize/ReentrantLock
使用volatile声明变量,轻量级同步,不能保证原子性(需要解释)
使用线程安全类(原子类AtomicXXX,并发容器,同步容器 CopyOnWriteArrayList/ConcurrentHashMap等
ThreadLocal本地私有变量/信号量Semaphore等

注意:本题回答的时候,尽量优先回答自己很熟悉底层原理保证线程安全的方式,防止被追问的一脸懵逼 

四、了解volatile关键字不?能否解释下,然后这和synchronized有什么大的区别 

volatile是轻量级的synchronized,保证了共享变量的可见性,被volatile关键字修饰的变量,如果值发生了变化,其他线程立刻可见,避免出现脏读现象

​区别:
volatile:保证可见性,但是不能保证原子性
synchronized:保证可见性,也保证原子性

使用场景:
1、不能修饰写入操作依赖当前值的变量,比如num++、num=num+1,不是原子操作,肉眼看起来是,但是JVM字节码层面不止一步
​2、由于禁止了指令重排,所以JVM相关的优化没了,效率会偏弱 

五、为什么会出现脏读? 

JAVA内存模型简称 JMM
JMM规定所有的变量存在在主内存,每个线程有自己的工作内存,线程对变量的操作都在工作内存中进行,不能直接对主内存就行操作

使用volatile修饰变量,每次读取前必须从主内存属性最新的值,每次写入需要立刻写到主内存中

volatile关键字修饰的变量随时看到的自己的最新值,假如线程1对变量v进行修改,那么线程2是可以马上看见

 六、你说volatile可以避免指令重排,能否解释下什么是指令重排

 指令重排序分两类 编译器重排序和运行时重排序

JVM在编译java代码或者CPU执行JVM字节码时,对现有的指令进行重新排序,主要目的是优化运行效率(不改变程序结果的前提)

int a = 3 //1
int b = 4 //2
int c =5 //3 
int h = a*b*c //4

定义顺序 1,2,3,4
计算顺序  1,3,2,4 和 2,1,3,4 结果都是一样


虽然指令重排序可以提高执行效率,但是多线程上可能会影响结果,有什么解决办法?
解决办法:内存屏障(这块想深挖,可以去搜一些博文帖子仔细看看)
解释:内存屏障是屏障指令,使CPU对屏障指令之前和之后的内存操作执行结果的一种约束

七、知道 happens-before吗,能否简单解释下? 

先行发生原则,volatile的内存可见性就体现了该原则之一()
例子:
//线程A操作
int k = 1;
​//线程B操作
int j = k;
​//线程C操作
int k = 2

分析:
假设线程A中的操作“k=1”先行发生于线程B的操作“j=k”,那确定在线程B的操作执行后,变量j的值一定等于1,依据有两个:一是先行发生原则,“k=1”的结果可以被观察到;二是第三者线程C还没出现,线程A操作结束之后没有其他线程会修改变量k的值。

但是考虑线程C出现了,保持线程A和线程B之间的先行发生关系,线程C出现在线程A和线程B的操作之间,但是线程C与线程B没有先行发生关系,那j的值会是多少?答案是1和2都有可能,因为线程C对变量k的影响可能会被线程B观察到,也可能不会,所以线程B就存在读取到不符合预期数据的风险,不具备多线程安全性 

八大原则(对这个不理解,可以补充相关博文知识)
1、程序次序规则
2、管程锁定规则
3、volatile变量规则
4、线程启动规则
5、线程中断规则
6、线程终止规则
7、对象终结规则
8、传递性​

 以下是并发编程相关博文,可以先看看AQS 读读里面的源码,然后再看ReetrantLock,会有一种通透的感觉~并发编程核心底层AQS你知道多少_这是王姑娘的微博的博客-CSDN博客一、你知道AQS吗?核心思想是什么?干嘛用的?二、源码看下重要的几个方法以及流程步骤三、​你知道的AQS有几种同步方式,实现同步器一般要覆盖哪些方法面试官:AQS你知道的吧,简单介绍一下面试的时候经常会被面试官这么猛的一问,支支吾吾半天没有很清晰的思路来做一个很好的回答,本博文重点针对AQS是什么,干什么用的,核心思想是什么,加锁/解锁的步骤是哪些、实现同步器的话哪些核心方法是必不可少的进行了描述,要耐心看完呀~一、你知道AQS吗?核心思想是什么?干嘛用的?AQS...https://blog.csdn.net/wnn654321/article/details/123584648

并发编程ReentrantLock实现原理和Synchroized区别_这是王姑娘的微博的博客-CSDN博客ReentrantLock是Lcok默认实现方式之一,它是基于AQS(AbstractQueuedSynchronizer 队列同步器)实现的,它默认是通过非公平锁实现的,在它的内部有一个state的状态字段用于表示锁是否被占用,如果是0则表示未被占用,此时线程就可以把state改成1,并成功获得了锁,而其他未获得锁的线程只能去排队等待获取资源。首先来看下ReentrantLock的两个构造函数: public ReentrantLock() { sync = new Nonhttps://blog.csdn.net/wnn654321/article/details/123590976 上机实战之写个不可重入锁和重入锁的例子_这是王姑娘的微博的博客-CSDN博客先来设计一个简单的不可重入锁不可重入锁:若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞/** * * 不可重入锁 简单例子 * * 不可重入锁:若当前线程执行某个方法已经获取了该锁,那么在方法中尝试再次获取锁时,就会获取不到被阻塞 */public class UnreentrantLock {​ private boolean isLocked = false;​ public synchronized void lockhttps://blog.csdn.net/wnn654321/article/details/122517193