多线程---volatile关键字
来源:互联网 发布:什么网络加速器最好 编辑:程序博客网 时间:2024/05/18 03:01
在多线程中,volatile关键字是很重要的一个知识点,在多线程共享资源的时候,每个线程数据对外都是不可见的,这就容易出现”脏读”现象,其实就是线程私有堆栈中的数据和公共堆栈中的数据不同步造成的.解决这样的问题,就要使用volatile关键字了。
- 内存结构
有这个结构图,就能很清晰的知道为毛会造成数据的不同步了。每个线程都会有各自的线程栈,执行运算的时候,是从公共堆栈读取数据到线程堆栈中,线程操作的是线程堆栈的数据,结束后,再从线程堆栈回刷到公共堆栈,所以这种肯定会引起数据的不同步。
那valotitle关键字有什么用,他是强制线程从公共堆栈中读取变量的值,以保证读取的是最新的,增加了实例变量的可见性。
来个demo验证下
public class MyThread extends Thread { private boolean isRun= true; @Override public void run() { System.out.println("run start"); while(isRun){ } System.out.println("run end"); } public void setRun(boolean isRun) { this.isRun = isRun; }} public static void main(String[] args) throws Exception { MyThread r = new MyThread(); r.start(); Thread.sleep(2000); r.setRun(false); }
打印结果:
线程一直处于运转状态,run end无法执行到;
修改一下假如volatitle关键字
public class MyThread extends Thread { volatile private boolean isRun= true; @Override public void run() { System.out.println("run start"); while(isRun){ } System.out.println("run end"); } public void setRun(boolean isRun) { this.isRun = isRun; }}
结果:
完全正确
但是volatile只解决了线程的可见性,并不能保证原子性
- 非原子性
来个demo验证:
public class MyThread extends Thread { volatile public static int count; @Override public void run() { add(); } private void add() { for(int i=0;i<100;i++){ count++; } System.out.println("count="+count); }}public static void main(String[] args) throws Exception { MyThread[] rs = new MyThread[100]; for(int i=0;i<rs.length;i++){ rs[i] = new MyThread(); } for(MyThread r:rs){ r.start(); } }
打印结果:
不等10000,那怎么改?只需要在add方法加synchronized锁,将add变为静态方法即可
synchronized private static void add() { for(int i=0;i<100;i++){ count++; } System.out.println("count="+count); }
结果:
关键字volatilt提示线程每次从共享内存中读取变量,而不是从私有内存中读取变量,保证了同步数据的可见性。但是这里需要注意的而是,修改实例变量中的数据,如i++,这样的操作其实不是一个原子操作,也就是非线程安全,表达式i++的操作分解步骤如下:
1)从内存中取i的值
2)计算i的值
3)将i的值写到内存中。
来看一下变量在内存中的工作过程:
在多线程环境中,use和assign是多次出现的,但这一操作并不是原子性,也就是在read和load之后,如果内存count变量发生修改之后,线程工作内存中的值由于已经加载,不会产生对应的变化,也就是私有内存和工作内存的变量不同步,所以计算出来的结果和预期不一样,也就是非线程安全问题。
对于volatile修饰的变量,JVM只是保证从主内存加载到工作内存中的值是最新的,例如线程1和2在进行read和load的操作中,发现主内存中的count的值都是5,那么都会加载这个最新的值,也就是说volatile关键字解决的是变量读时的可见性问题,但不保证原子性,对于多个线程访问同一个实例变量还是需要加同步锁。
- 原子类进行操作
原子操作是不能分割的整体,没有其他线程能够中断或检查正在原子操作中的变量,一个原子类型(Atomicxxx)就是一个原子操作可用的类型,他可以在没有锁的情况下做到线程安全。
public class MyThread extends Thread { private AtomicInteger atomic = new AtomicInteger(0); @Override public void run() { add(); } private void add() { for(int i=0;i<100;i++){ System.out.println("count="+atomic.incrementAndGet()); } }}public static void main(String[] args) throws Exception { MyThread r = new MyThread(); Thread[] rs = new Thread[100]; for(int i=0;i<rs.length;i++){ rs[i] = new Thread(r); } for(Thread m:rs){ m.start(); } }
成功累加到10000
- 原子类也并不完全安全
public class MyThread extends Thread { public static AtomicInteger atomic = new AtomicInteger(0); @Override public void run() { add(); } private void add() { System.out.println("count="+atomic.addAndGet(100)); atomic.addAndGet(1); }}public static void main(String[] args) throws Exception { MyThread r = new MyThread(); Thread[] rs = new Thread[5]; for(int i=0;i<rs.length;i++){ rs[i] = new Thread(r); } for(Thread m:rs){ m.start(); } Thread.sleep(2000); System.out.println(MyThread.atomic.get()); }
结果的一种可能:
虽然最终结果是对的,但是打印明显不符合预期,+1这个明显没有打印出来。虽然add方法是原子操作,但是调用方法却不是原子的,解决这种问题,必须用同步。
改进后的
public class MyThread extends Thread { public static AtomicInteger atomic = new AtomicInteger(0); @Override public void run() { add(); } synchronized private void add() { System.out.println("count="+atomic.addAndGet(100)); atomic.addAndGet(1); }}
打印结果:
- Java多线程 -- volatile关键字
- java多线程--volatile关键字
- 多线程之volatile关键字
- Java 多线程:volatile关键字
- volatile 多线程同步关键字
- 多线程-关键字Volatile
- 多线程之volatile关键字
- 多线程---volatile关键字
- java 多线程 volatile 关键字
- 多线程中的volatile关键字
- 多线程 volatile关键字
- 【java多线程 关键字】volatile
- 多线程 说说volatile关键字
- 多线程之volatile关键字
- Java多线程的volatile关键字
- JAVA 多线程之~~volatile关键字
- 多线程(五):Volatile 关键字
- java多线程之volatile关键字
- Android教学中遇到的内存泄露和内存溢出
- 设计模式六大原则(3):依赖倒置原则
- 【个人随笔】这一年,我们再也没有开学
- logback logback.xml常用配置详解(三) <filter>
- #pragma once 和#ifndef的区别
- 多线程---volatile关键字
- c指针的各种用法试验
- LoRa笔记01 sx1276 sx1278信号强度RSSI研究
- 学习记录——数据链路层(3)
- synchronized(this)、synchronized(class)与synchronized(Object)的区别
- 数组中重复的数字
- 设计模式六大原则(4):接口隔离原则
- 备忘录模式
- 关于Android Studio时出现cannot resolve symbol R报错的解决方法