多线程访问变量的不可见性
- 多个线程访问共享变量,会出现一个线程修改变量的值后,其他线程看不到变量最新值的情况。
public class VolatileDemo01 {
public static void main(String[] args) {
// 1.启动线程,把线程对象中的flag改为true。
VolatileThread t = new VolatileThread();
t.start();
// 2.定义一个死循环
while(true){
// 这里读取到了flag值一直是false,虽然线程已经把它的值改成了true。
if(t.isFlag()){
System.out.println("执行了循环一次~~~~~~~");
}
}
}
}
// 线程类。
class VolatileThread extends Thread {
private boolean flag = false ;
public boolean isFlag() {
return flag;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
// 将flag的值更改为true
this.flag = true ;
System.out.println("线程修改了flag=" + flag);
}
}
- 如上代码,尽管在子线程中,已经对
flag
的值进行过修改,但是这个修改对于主线程来说是不可见的。
不可见性的原因
- JMM(Java Memory Model)是Java虚拟机规范中所定义的一种内存模型;
- Java内存模型描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存中读取变量这样的底层细节
- 所有共享变量都存储在主内存。
-
线程的工作内存,保留了被线程使用的变量的工作副本。线程对变量所有的操作都必须在工作内存中完成,而不能直接读写主内存中变量。不同线程之间也不能直接访问。
不可见性的解决方法
- 1.加锁
- 加锁可以实现其他线程对于变量修改的可见性
- 原因:某一个线程进入
synchronized
代码块前后会有如下操作 :- 线程获得锁
- 清空工作区内存
- 从主内存拷贝共享变量的最新值到工作内存作为副本
- 执行代码
- 将修改后的副本的值刷新回主内存中
- 线程释放锁
public class Mytest1 {
public static void main(String[] args) {
// 1.启动线程,把线程对象中的flag改为true。
VolatileThread t = new VolatileThread();
t.start();
// 2.定义一个死循环
while(true){
// 这里读取到了flag值一直是false,虽然线程已经把它的值改成了true。
synchronized (Mytest1.class){
if(t.isFlag()){
System.out.println("执行了循环一次~~~~~~~");
}
}
}
}
}
// 线程类。
class VolatileThread extends Thread {
private boolean flag = false ;
public boolean isFlag() {
return flag;
}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
// 将flag的值更改为true
this.flag = true ;
System.out.println("线程修改了flag=" + flag);
}
}
- 2.使用
volatile
关键字- 可以给成员变量加上
volatile
关键字,当一个线程修改了这个成员变量的值,其他线程可以立即查看到修改后的值并使用。
- 可以给成员变量加上
-
synchronized 和 volatile 区别
- volatile只能修饰实例变量和类变量,而synchronized可以修饰方法,以及代码块;
- volatile保证数据的可见性,但是不保证数据的原子性,而synchronized是一种排他的机制。
原子性
- 原子性是指在一次操作或者多次操作中,所有的操作全部都得到了执行并且不会受到任何因素的干扰。
- 原子性问题实例
public class VolatileAtomicThread implements Runnable {
// 定义一个int类型的遍历
private volatile int count = 0 ;
@Override
public void run() {
// 对该变量进行++操作,100次
for(int x = 0 ; x < 100 ; x++) {
count++ ;
System.out.println("count =========>>>> " + count);
}
}
}
class VolatileAtomicThreadDemo {
public static void main(String[] args) {
// 创建VolatileAtomicThread对象
Runnable target = new VolatileAtomicThread() ;
// 开启100个线程对执行这一个任务。
for(int x = 0 ; x < 100 ; x++) {
new Thread(target).start();
}
}
}
- 原子性问题解决方案:
- 1.加锁,但是加锁机制性能较差
- 2.使用原子类
原子类
- Java已经提供了一些本身即可实现原子性(线程安全)的类
-
Java从JDK 1.5开始提供了
java.util.concurrent.atomic
- 例如对于整型操作的原子类
public AtomicInteger(): 初始化一个默认值为0的原子型Integer
public AtomicInteger(int initialValue): 初始化一个指定值的原子型Integer
int get(): 获取值
int getAndIncrement(): 以原子方式将当前值加1,注意,这里返回的是自增前的值。
int incrementAndGet(): 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data): 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value): 以原子方式设置为newValue的值,并返回旧值。
public class VolatileAtomicThread implements Runnable {
// 原子类中封装好了整型变量,默认值是0
private AtomicInteger atomicInteger = new AtomicInteger();
@Override
public void run() {
// 对该变量进行++操作,100次
for(int x = 0 ; x < 100 ; x++) {
int count = atomicInteger.incrementAndGet(); // 底层变量+1且返回!
System.out.println("count =========>>>> " + count);
}
}
}
class VolatileAtomicThreadDemo {
public static void main(String[] args) {
// 创建VolatileAtomicThread对象
Runnable target = new VolatileAtomicThread() ;
// 开启100个线程对执行这一个任务。
for(int x = 0 ; x < 100 ; x++) {
new Thread(target).start();
}
}
}
网友评论