码迷,mamicode.com
首页 > 其他好文 > 详细

面试问题

时间:2019-12-02 23:55:12      阅读:189      评论:0      收藏:0      [点我收藏+]

标签:情况下   期望   基于   生成   tar   使用   字节   ring   编程   

-----2019-12-2

1、Volatile关键字的粗浅理解
在学习并发编程的时候了解到,volatile关键字有两个作用:

1. 并发环境可见性:volatile修饰后的变量能够保证该变量在线程间的可见性,线程进行数据的读写操作时将绕开工作内存(CPU缓存)而直接跟主内存进行数据交互,即线程进行读操作时直接从主内存中读取,写操作时直接将修改后端变量刷新到主内存中,这样就能保证其他线程访问到的数据是最新数据

2. 并发环境有序性:通过对volatile变量采取内存屏障(Memory barrier)的方式来防止编译重排序和CPU指令重排序,具体方式是通过在操作volatile变量的指令前后加入内存屏障,来实现happens-before关系,保证在多线程环境下的数据交互不会出现紊乱

Volatile无法保证原子性
由于volatile关键字的可见性,导致容易被误解其作用,以下描述是不准确的:“volatile变量对所有线程是立即可见的,对volatile变量所有的写操作都能立即反馈到其他线程中,volatile变量在各个线程中都是一致的,所以基于volatile变量的运算在并发下安全的”

使用volatile关键字虽然能够使线程共享的变量在并发情况下完全可见,起到线程信息交互和通信的作用,但对于非原子操作,volatile并不能保证该操作的原子性(即操作过程被其他线程干扰导致信息错误和信息丢失),最简单的例子就是i++这样的自增操作,以下是一个并发情况下的自增例子:

private static volatile int count = 0;

public static void main(String[] args){
Thread[] threads = new Thread[5];
for(int i = 0; i<5; i++){
threads[i] = new Thread(()->{
try{
for(int j = 0; j<10; j++){
System.out.println(++count);
Thread.sleep(500);
}
}catch (Exception e){
e.printStackTrace();
}
});
threads[i].start();
}
}
生成5条线程,每条线程都对count执行10次自增操作,按理说最后的结果应该是1到50均被打印出来,但不管运行多少次,都无法得到期望的结果,说明volatile标记的变量在并发环境下并不能保证线程安全。

诸如“i++”或者“++i”这样的操作并不是原子操作,因为自增操作包括三个基本指令:读取数据、计算数据、输出结果,可以看看i++相关的字节码:

Code:
0: getstatic #2 // Field count:I
3: iconst_1
4: iadd
5: putstatic #2 // Field count:I
8: return
getstatic指令将变量从主内存中读取出来,这时候如果该变量时volatile修饰的,那可以完全保证此时取到的是最新信息,但在iconst_1指令(入栈)和iadd指令(自增计算)执行过程中,该变量有可能正在被其他线程修改,最后计算出来的结果照样存在问题,因此volatile并不能保证非原子操作的原子性,仅在单次读或者单次写这样的原子操作中,volatile能够实现线程安全

Volatile结合CAS实现原子性
2、CAS(CompareAndSwap)

比较交换原则结合volatile,就能够实现基本的线程安全,典型的应用就是concurrent包下的Atomic类,上述例子如果用AtomicInteger代替int,就能够实现自增情况下的线程安全

3、堆排序?

 

 

 

 

 

 

 

 

 

 

 

 

面试问题

标签:情况下   期望   基于   生成   tar   使用   字节   ring   编程   

原文地址:https://www.cnblogs.com/erfsfj-dbc/p/11973864.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!