系列文章目录
简述
关键字 volatile 的主要作用是使变量在多个线程间可见
解决死循环
示例
class ThreadTest7 implements Runnable{
private boolean isRunnning = true;
@Override
public void run() {
System.out.println("Thread begin: "+Thread.currentThread().getName());
while (isRunnning == true){
}
System.out.println("Thread end: "+Thread.currentThread().getName());
}
public boolean isRunnning() {
return isRunnning;
}
public void setRunnning(boolean runnning) {
isRunnning = runnning;
}
public static void main(String[] args) throws InterruptedException {
ThreadTest7 printString = new ThreadTest7();
Thread thread = new Thread(printString,"Thread-A");
thread.start();
Thread.sleep(100);
printString.setRunnning(false);
System.out.println(" 我要停止它!" + Thread.currentThread().getName());
}
}
运行结果:
Thread begin: Thread-A
我要停止它!main
解析代码:isRunnning变量被主线程做了修改,因此thread线程取到的值,理应为false,而线程也应该会停止,但是从运行结果看了,线程没有运行结束,仍旧在死循环。
分析
是什么原因?为什么线程没有停下来?
JVM 有 Client 和 Server 两种模式,我们可以通过运行:java -version 来查看 jvm 默认工作在什么模式。我们在 IDE 中把 JVM 设置为在 Server 服务器的环境中,具体操作只需配置运行参数为 -server。
通过在cmd上使用java -version,可以看到我的jvm运行模式如下:
C:\Users\59357>java -version
java version “1.8.0_181”
Java™ SE Runtime Environment (build 1.8.0_181-b13)
Java HotSpot™ 64-Bit Server VM (build 25.181-b13, mixed mode)
可以看到,我当前jvm的运行模式是Server,上面的代码运行起来没毛病的,会出现死循环。(注意:在Client 模式下,线程会被停止,我们在 IDE 中把 JVM 设置为在 Server 服务器的环境中,具体操作只需配置运行参数为 -client 。)
是什么样的原因造成将 JVM 设置为 -server 就出现死循环呢?
在启动 thread 线程时,变量 boolean isContinuePrint = true; 存在于公共堆栈及线程的私有堆栈中。
在 JVM 设置为 -server 模式时为了线程运行的效率,线程一直在私有堆栈中取得 isRunning 的值是 true。
而代码 thread.setRunning(false); 虽然被执行,更新的却是公共堆栈中的 isRunning 变量值 false,所以一直就是死循环的状态。
这个问题其实就是私有堆栈中的值和公共堆栈中的值不同步造成的。解决这样的问题就要使用 volatile 关键字了,它主要的作用就是当线程访问 isRunning 这个变量时,强制性从公共堆栈中进行取值。
解决的办法是使用 volatile 关键字。
关键字 volatile 的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值。
注:当循环中打印一句话时,循环会结束,具体原因未知
将代码更改如下:
volatile private boolean isRunnning = true;
线程就会从公共堆栈中取值。
不能保证原子性
与synchronized比较
关键字 volatile 是线程同步的轻量级实现,所以 volatile 性能肯定比 synchronized 要好,并且 volatile 只能修饰于变量,而 synchronized 可以修饰方法,以及代码块。随着 JDK 新版本的发布,synchronized 关键字在执行效率上得到很大提升,在开发中使用 synchronized 关键字的比率还是比较大的。
多线程访问 volatile 不会发生阻塞,而 synchronized 会出现阻塞。
volatile 能保证数据的可见性,但不能保证原子性;而 synchronized 可以保证原子性,也可以间接保证可见性,因为它会将私有内存和公共内存中的数据做同步。
再次重申一下,关键字 volatile 解决的是变量在多个线程之间的可见性;而 synchronized 关键字解决的是多个线程之间访问资源的同步性。
线程安全包含原子性和可见性两个方面,Java 的同步机制都是围绕这两个方面来确保线程安全的。
volatile 非原子性的特征
class ThreadTest8 extends Thread {
volatile private static int count;
@Override
public void run() {
addCount();
}
private void addCount() {
for (int i = 0;i<100;i++){
count++;
}
System.out.println(count);
}
public static void main(String[] args) {
int size = 100;
ThreadTest8[] myThreads = new ThreadTest8[size];
for (int i=0;i<size;i++){
myThreads[i] = new ThreadTest8();
}
for (int i=0;i<size;i++){
myThreads[i].start();
}
}
}
运行结果:
…825383538153805378757675
在 addCount 方法上加入 synchronized 同步关键字与 static 关键字,达到同步的效果。
再次运行结果:
…960097009800990010000
关键字 volatile 提示线程每次从共享内存中读取变量,而不是从私有内存中读取,这样就保证了同步数据的可见性。但在这里需要注意的是:如果修改实例变量中的数据,比如 i++,也就是比
i=i+1,则这样的操作其实并不是一个原子操作,也就是非线程安全。表达式 i++ 的操作步骤分解为下面三步:
• 从内存中取 i 的值;
• 计算 i 的值;
• 将 i 值写入到内存中。
假如在第二步计算 i 值的时候,另外一个线程也修改 i 的值,那么这个时候就会脏数据。解决的方法其实就是使用 synchronized 关键字。所以说 volatile 关键字本身并不处理数据的原子性,而是强制对数据的读写及时影响到主内存中。