一:volatile多线程可见性
volatile的关键字的作用是使变量在多个线程间可见
如下代码:
public class PrintStringA {
private boolean isContinuePrint = true;
public void setContinuePrint(boolean isContinuePrint) {
this.isContinuePrint = isContinuePrint;
}
public void printStringMethod() {
try {
while (isContinuePrint == true) {
System.out.println("run printStringMethod threadName = " +
Thread.currentThread().getName());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class RunA {
public static void main(String[] args) {
PrintStringA printStringService = new PrintStringA();
printStringService.printStringMethod(); (1)
System.out.println("我要停止他 stopThread = " + Thread.currentThread().getName()); (2)
printStringService.setContinuePrint(false); (3)
}
}
执行结果:
run printStringMethod threadName = main
run printStringMethod threadName = main
run printStringMethod threadName = main
run printStringMethod threadName = main
main线程会一直执行下去。因为此时的程序只有一个线程,而方法内的语句是按照顺序执行的,第(1)没有执行完成,第(2)和(3)句就不会执行。解决办法就是使用多线程。
使用多线程后的代码:
public class PrintStringB implements Runnable {
private boolean isContinuePrint = true;
public void setContinuePrint(boolean isContinuePrint) {
this.isContinuePrint = isContinuePrint;
}
public void printStringMethod() {
try {
while (isContinuePrint == true) {
System.out.println("run printStringMethod threadName=" +
Thread.currentThread().getName());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
printStringMethod();
}
}
public class RunB {
public static void main(String[] args) {
PrintStringB printStringService = new PrintStringB();
new Thread(printStringService).start();
System.out.println("我要停止他 stopThread = " + Thread.currentThread().getName());
printStringService.setContinuePrint(false);
}
}
在idea下执行结果如下:
我要停止他 stopThread = main
但是如果运行在-server服务器模式中的JVM时,执行结果如下:
run printStringMethod threadName = main
run printStringMethod threadName = main
run printStringMethod threadName = main
run printStringMethod threadName = main
为什么会这样呢?因为在启动RunB时,变量 isContinuePrint 会存在公共内存(主内存)和线程的私有内存中,为了效率,线程会一直从私有内存中取 isContinuePrint 的值为true,而setContinuePrint更新的是公共内存的变量,所以程序会死循环。解决的办法就是使用volatile。
使用volatile后的代码:
public class PrintStringC implements Runnable {
private volatile boolean isContinuePrint = true; // 将其设为 volatile 强制从公共内存取值
public void setContinuePrint(boolean isContinuePrint) {
this.isContinuePrint = isContinuePrint;
}
public void printStringMethod() {
try {
while (isContinuePrint == true) {
System.out.println("run printStringMethod threadName=" +
Thread.currentThread().getName());
Thread.sleep(1000);
}
System.out.println("我被停止了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void run() {
printStringMethod();
}
}
public class RunC {
public static void main(String[] args) {
try {
PrintStringC printStringService = new PrintStringC();
new Thread(printStringService).start();
Thread.sleep(3000);
System.out.println("我要停止他 stopThread = " + Thread.currentThread().getName());
printStringService.setContinuePrint(false);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果:
run printStringMethod threadName=Thread-0
run printStringMethod threadName=Thread-0
run printStringMethod threadName=Thread-0
我要停止他 stopThread = main
我被停止了
通过使用volatile关键字,强制的从公共内存中读取变量的值,增加了实例变量在多个线程间的可见性,
但是volatile有个缺点就是不支持原子性。
二:volatile的非原子性
变量在内存中的工作过程图
关键词volatile主要使用场合是在多个线程中感知实例变量(只有实例变量是非线程安全的,局部变量是线程安全的)被更改了,并且可以获得最新的值使用,但是不保证操作是原子性的。
例如,进行 count++ 这样的操作,即count = count + 1,拆解如下:
1,从主存中取出count的值放入工作内存,(对应read和load加载)
2,在工作内存中进行计算,(对应use操作和assign赋值)
3,将工作区的结果写入到主存中,(对应store和write)
其中当我们从主存中取出最新的count之后, 可能其他线程也更改了count的值,这样就导致了不同步。所以volatile只可以保证变量读时的可见性。对于多个线程访问同一个实例变量还需要加锁同步。
三:原子类和synchronized
解决volatile可以使用Atomic原子类实现,没有其他线程可以中断和检查正在原子操作中的变量。他可以在没有锁的情况下做到线程安全。第二种是用synchronized关键字。
synchronized可以使多个线程访问同一个资源具有同步性,而且他还具有将线程工作内存中的私有变量与公共内存中的变量同步的功能。
参考:《Java多线程编程核心技术》