例子
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