小知识点记录: 可见性与原子性
Posted 小智RE0
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小知识点记录: 可见性与原子性相关的知识,希望对你有一定的参考价值。
文章目录
可见性
先看这样一段代码,运行是没有问题的
boolean flag = true;
flag = false;
System.out.println(flag);
但是,若在 赋值false之前,加入一段逻辑处理时;
boolean flag = true;
//加入一段线程处理代码;
new Thread( ()->
while(flag)
).start();
flag = false;
System.out.println(flag);
可设想是这样运行的;
实际代码尝试使用;
public class Demo1
static boolean flag = true;
public static void main(String[] args)
//加入一段线程处理代码;
new Thread(() ->
while (flag)
).start();
flag = false;
System.out.println(flag);
但是,如果为线程设定休眠1秒时
;虽然打印输出了false;但是线程一直在运行
,这个while循环一直在进行;
- 从代码的角度来看, 这里
flag = false;
就是一个写操作; - 而
while(flag)
这个循环是一个读操作. - 那么为什么输出了false后线程还在继续运行呢? 这样的一个状况: 一个线程已经将
变量flag
写操作变为了false;但是其他的线程并不知道,即并不可见,也就会发生线程一直循环的状况出现.
现在将线程的休眠时间变为1毫秒
试试;此时程序即可成功的结束;
浅画一下内存的图例;
在某种情况下,CPU的多个core使用不同的缓存;而CPU的速度过快,为了平和这个速度的差距,在CPU中使用
local-cache
三级缓存来解决速度问题.
就从刚才的案例来看,在主存中有原始的flag=true
;
假设一个core核心负责while(flag) 读取flag的值, 但是读取时一直读取时都是从缓存中读取;
有一个负责主线程运行的core已经将主存中的flag
变为flase了,但是while操作的线程还是无法及时看到.
在任务管理器中查看CPU状态,可看到L1,L2,L3的分级缓存显示.
这里的local-cache三级缓存
,可以说是最快的CPU交互区域
, 比主存要快的多;
从L1 ---> L3级别速度是依次降低的
.
可使用 volatile 关键字
解决共享变量的可见性问题;即使线程休眠5秒,最后也会停止;
原子性
需要注意原子性是建立在可见性的基础上;
volatile
无法保证原子性,
- 用
volatile
修饰的对象内部属性并不具有可见性,- 用
volatile
修饰的内部属性也无法保证所在对象的课件性.
刚才的案例是,一个线程负责读操作,一个线程负责写操作.
现在有一个案例, 线程读取变量的值以后再对该变量进行赋值写操作,即i++问题
;
一个 i++
操作实际可看做三个操作:
- (1)读取
变量i
的值; - (2)执行
i+1
; - (3)将结果赋值给i;
在这样的一个i++
问题中,假设主内存中初始值i=0
,线程A和线程B都对这个值进行操作,那么最终可能会出现i=1
或者i=2
;
案例模拟:
public class Demo2
static volatile int i = 0;
public static void main(String[] args) throws InterruptedException
for (int j = 0; j < 10000; j++)
//线程A;
new Thread(()->
i++;
).start();
//线程B;
new Thread(()->
i++;
).start();
Thread.sleep(1000);
System.out.println(i);
预期执行得到的结果为20000,但是并不是预期的值;
实际上,volatile 只能保证可见性,不可保证原子性;
那么在这里案例中,要保证原子性的话,可以使用并发包下的AtomicInteger
类;
public class Demo3
static AtomicInteger i = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException
for (int j = 0; j < 10000; j++)
//线程A;
new Thread(()->
i.getAndAdd(1);
).start();
//线程B;
new Thread(()->
i.getAndAdd(1);
).start();
Thread.sleep(1000);
System.out.println(i);
当然要保证原子性,也可以从同步的角度处理;
比如使用synchronized
关键字;保证同一时间只能有一个线程来执行代码块;
public class Demo4
static int i = 0;
public static void main(String[] args) throws InterruptedException
for (int j = 0; j < 10000; j++)
//线程A;
new Thread(() ->
synchronized (Demo4.class)
i++;
).start();
//线程B;
new Thread(() ->
synchronized (Demo4.class)
i++;
).start();
Thread.sleep(1000);
System.out.println(i);
以上是关于小知识点记录: 可见性与原子性的主要内容,如果未能解决你的问题,请参考以下文章