在多线程中,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只解决了线程的可见性,并不能保证原子性
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;inew 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关键字解决的是变量读时的可见性问题,但不保证原子性,对于多个线程访问同一个实例变量还是需要加同步锁。
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;inew Thread(r);
}
for(Thread m:rs){
m.start();
}
}
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;inew 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);
}
}