JUC-001-volatile与内存可见性
2017-11-19 本文已影响10人
53b3f4658edc
JUC简介
- 在Java 5.0 提供了java.util.concurrent(简称JUC )包,在此包中增加了在并发编程中很常用的实用工具类,用于定义类似于线程的自定义子系统,包括线程池、异步IO 和轻量级任务框架。提供可调的、灵活的线程池。还提供了设计用于多线程上下文中的Collection 实现等。
内存可见性(Memory Visibility)
- 内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象状态后,其他线程能够看到发生的状态变化。
- 可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情。
- 我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的volatile 变量。
测试代码
package top.itcourse._volatile;
import org.junit.Test;
import org.omg.CORBA.FloatSeqHelper;
/*
* 内存可见性(Memory Visibility):
* - 内存可见性(Memory Visibility)是指当某个线程正在使用对象状态而另一个线程在同时修改该状态,需要确保当一个线程修改了对象
* 状态后,其他线程能够看到发生的状态变化。
* - 可见性错误是指当读操作与写操作在不同的线程中执行时,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根
* 本不可能的事情。
* - 我们可以通过同步来保证对象被安全地发布。除此之外我们也可以使用一种更加轻量级的volatile 变量。
*
* 说明:
* 线程对象中的属性是放在主存中的,线程修改/读取的属性是在自己线程的缓存中,当修改完毕之后再把值
* 放回主存(不同的线程中去读取/修改的属性值,都是主存中的值)
*/
/*
* 下面我们实现:
* 线程一、二都读取到flag的初始值,然后在线程一的run方法中改变flag的值,但是线程二的flag值没有同步更新
*/
public class TestMemVisi {
@Test
public void testMemVisi() throws InterruptedException {
// 2.新建实现了Runnable的对象
MemVisi mv1 = new MemVisi();
// 3.传递到Thread对象中start
new Thread(mv1).start();
while(true) {
// 线程二:一直是false(因为线程一sleep的时候,它进来了,又因为是while循环,使得线程二根本没有机会再次从主存中去读取新值(可以说明
// 没有线程间没有同步的通知、更新))
if( mv1.isflag() ) {
System.out.println("我在主线程打印,flag是true");
}
// Thread.sleep(50); // 加上这句话,就可以有机会去主存中读取新值
}
}
}
// 1.实现Runnable
class MemVisi implements Runnable {
private boolean flag = false;
public boolean isflag() {
return flag;
}
public void setflag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
// 让线程二先进去判断(while判断)
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
// 线程一:
flag = true;
System.out.println("flag: " + flag);
}
}
运行结果:
flag: true
volatile 关键字
-
Java提供了一种稍弱的同步机制,即volatile 变量,用来确保将变量的更新操作通知到其他线程。可以将volatile 看做一个轻量级的锁,但是又与锁有些不同:
- 对于多线程,不是一种互斥关系
- 不能保证变量状态的“原子性操作”
-
计算机在运行程序时,每条指令都是在CPU中执行的,在执行过程中势必会涉及到数据的读写。我们知道程序运行的数据是存储在主存中,这时就会有一个问题,读写主存中的数据没有CPU中执行指令的速度快,如果任何的交互都需要与主存打交道则会大大影响效率,所以就有了CPU高速缓存。CPU高速缓存为某个CPU独有,只与在该CPU运行的线程有关。
-
有了CPU高速缓存虽然解决了效率问题,但是它会带来一个新的问题:数据一致性。在程序运行中,会将运行所需要的数据复制一份到CPU高速缓存中,在进行运算时CPU不再也主存打交道,而是直接从高速缓存中读写数据,只有当运行结束后才会将数据刷新到主存中。
-
解决方案:
- 加锁
- 独占,只能有一个运行,其它都得阻塞
- 缓存一致性协议(volatitle)
-
缓存一致性协议(MESI协议)它确保每个缓存中使用的共享变量的副本是一致的。其核心思想如下:当某个CPU在写数据时,如果发现操作的变量是共享变量,则会通知其他CPU告知该变量的缓存行是无效的,因此其他CPU在读取该变量时,发现其无效会重新从主存中加载数据。
微信公众号:JavaWeb架构师
-
- 加锁
测试代码
package top.itcourse._volatile;
import org.junit.Test;
/*
* volatile关键字:
* Java提供了一种稍弱的同步机制,即volatile 变量,用来确保将变量的更新操作通知到其他线程。可以将volatile 看做一个轻量级的锁,但是又与锁有些不同:
* - 对于多线程,不是一种互斥关系(同步通知,所以效率会高一些)
* - 不能保证变量状态的“原子性操作”(下节讲)
*
* 作用:多个线程操作共享数据时,保证内存中的数据是可见的
*/
/*
* 解决同步通知、更新的问题:
* 1.使用锁(效率低)
* 2.
*/
public class TestVolatile {
@Test
public void testMemVisi() throws InterruptedException {
// 2.新建实现了Runnable的对象
MemVisia mv1 = new MemVisia();
// 3.传递到Thread对象中start
new Thread(mv1).start();
while(true) {
// 使用synchronized,可以解决,会去刷新缓存
// synchronized(mv1) {
// if( mv1.isflag() ) {
// System.out.println("我在主线程打印,flag是true");
// break;
// }
// }
if( mv1.isflag() ) {
System.out.println("我在主线程打印,flag是true");
break;
}
}
}
}
// 1.实现Runnable
class MemVisia implements Runnable {
// 使用volatile关键字,就可以解决了
private volatile boolean flag = false;
public boolean isflag() {
return flag;
}
public void setflag(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
// 让线程二先进去判断(while判断)
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
// 线程一:
flag = true;
System.out.println("flag: " + flag);
}
}
结果:
我在主线程打印,flag是true
flag: true
其它
关注下方微信公众号,
回复:
JUC.code
-
欢迎加入交流群:451826376
-
更多信息:www.itcourse.top