高并发--卷2--有序性--volatile

例子

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


public class T{
    public static boolean flag = true;
    public static int i = 0;
    public static void main(String[] args) throws InterruptedException, IOException {
        new Thread(new Mythread()).start(); //语句1
        loadContext();      //语句2
        flag = false;       //语句3
        Thread.sleep(50);
    }
    public static void loadContext() throws InterruptedException, IOException{
        File file = new File("d://1.txt");
        FileInputStream fis = new FileInputStream(file);
        int b;
        while((b = fis.read()) != -1){
            //doSomething
        }
        fis.close();
        System.out.println("load-end");
    }
}
class Mythread implements Runnable{
    @Override
    public void run() {
        while(T.flag){
            T.i++;
            try {
                Thread.sleep(10);           //这句使得公共内存与私有内存同步
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("thread-end");
        T.flag = true;
    }
}

可能产生的输出结果:
thread-end
load-end

解读

事实上,这就涉及到了程序的有序性,就java虚拟机而言,在编译的时候,它会对程序进行优化,也就是说,语句2不一定在语句3之前执行,虽然这两句并没有实质性的关系,但是在多线程环境下就可能出现问题。事实上,java虚拟机在将其进行编译的时候,它会对程序语句进行重排列,也就是说,你代码是这么写的,但是它的执行顺序不一定是这样的,当然,在单线程环境下,它能保证计算机不出错。
对于前后没有依赖关系的两句指令,那么就可能发生重排序。这里可以阅读参考文献。
那么如何让语句3在语句2之后才执行了,也就是说,等loadContext()执行完成之后才执行呢?
这里依然是使用volatile关键字。经过volatile关键字修饰后的语句,它能保证,在它前面的代码一定在它之前执行,而在它后面的代码,一定在它后面执行,这也就在一定程度上保证了程序的有序性。修改后的例子为:

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;


public class T{
    public static boolean flag = true;
    public static int i = 0;
    public static void main(String[] args) throws InterruptedException, IOException {
        new Thread(new Mythread()).start(); //语句1
        loadContext();      //语句2
        flag = false;       //语句3
        Thread.sleep(50);
    }
    public static void loadContext() throws InterruptedException, IOException{
        File file = new File("d://1.txt");
        FileInputStream fis = new FileInputStream(file);
        int b;
        while((b = fis.read()) != -1){
            //doSomething
        }
        fis.close();
        System.out.println("load-end");
    }
}
class Mythread implements Runnable{
    @Override
    public void run() {
        while(T.flag){
            T.i++;
            try {
                Thread.sleep(10);           //这句使得公共内存与私有内存同步
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("thread-end");
        T.flag = true;
    }
}

单例模式与volatile

在多线程下,为了保证线程安全,通常要使用synchronized锁同时使用

public class Singleton {
    private volatile static Singleton uniqueInstance;
    private Singleton(){}
    public static Singleton getInstance(){
        if(uniqueInstance == null){
        // B线程检测到uniqueInstance不为空
            synchronized(Singleton.class){
                if(uniqueInstance == null){
                    uniqueInstance = new Singleton();
                    // A线程被指令重排了,刚好先赋值了;但还没执行完构造函数。
                }
            }
        }
        return uniqueInstance;// 后面B线程执行时将引发:对象尚未初始化错误。
    }
}

以上引用的是参考文献2中的一个例子,通过注释,你可以发现,A线程还没吧Singleton初始化完,B线程就把Singleton给返回了,由于构造函数可能还要执行剩下多个步骤,可能导致Singleton是一个缺乏某些属性的对象。
如果增加了volatile关键字,Singleton的构造是有序的,也就是说A线程一次完成了对它的构造,在CPU把B线程返回Singleton的指令放到了Singleton构造完成之后,从而解决了Singleton
构造不完整的问题。

参考:
https://www.cnblogs.com/dolphin0520/p/3920373.html
https://blog.csdn.net/jm_heiyeqishi/article/details/51052889
https://blog.csdn.net/garfielder007/article/details/51160707

猜你喜欢

转载自blog.csdn.net/qq_25956141/article/details/81001416
今日推荐