Java——多线程高并发系列之volatile关键字
Posted 张起灵-小哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java——多线程高并发系列之volatile关键字相关的知识,希望对你有一定的参考价值。
文章目录:
写在前面:synchronized和volatile关键字的作用、区别?
写在前面:synchronized和volatile关键字的作用、区别?
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
● 保证了不同线程对这个变量进行操作时的可见性,不保证原子性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
● 禁止进行指令重排序。
- volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。
- volatile仅能使用在变量级别;synchronized则可以使用在变量、方法级别的。
- volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可见性和原子性。
- volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。
- volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。
Demo1(不使用volatile,不保证可见性)
我们说volatile只能用来修饰变量,那么这个例子中,没有使用volatile修饰静态内部类中的continuePrint变量,它的初始值为true,在main线程中,开启子线程之后,它会去执行printStringMethod方法,然后打印打印一句话,之后是一个循环while(continuePrint) {} ,对于这个子线程来说,它的这个while循环是一直成立的,所以它会一直在这执行这个空内容的while循环。而main睡眠1秒之后,它将continuePrint的值修改为了false,那么这个时候子线程能否知道、并且终止while循环呢?答案是不能,因为continuePrint这个局部变量是子线程独占的,我们都知道局部变量存储在栈空间中,而每个线程的栈空间都是独立不共享的,它们共享的仅仅是堆区和方法区。所以即使你的main主线程修改了continuePrint变量为false,但是在子线程的眼中,continuePrint变量仍然为true。所以执行结果中就会一直卡在这里了。
package com.szh.volatiletest;
/**
* volatile保证可见性
*/
public class Test01 {
public static void main(String[] args) {
PrintString ps=new PrintString();
//开启子线程,让子线程执行 ps 对象中的 printStringMethod() 方法
new Thread(new Runnable() {
@Override
public void run() {
ps.printStringMethod();
}
}).start();
//main线程睡眠1000ms
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("在main线程中修改打印标志");
/*
main线程在这里对 continuePrint 做了修改,但是子线程中是读取不到的
解决办法:使用 volatile 关键字修饰 continuePrint
volatile 关键字的作用可以强制线程从公共内存中读取数据,而不是从工作内存中读取
*/
ps.setContinuePrint(false);
}
static class PrintString {
private boolean continuePrint=true;
public PrintString setContinuePrint(boolean continuePrint) {
this.continuePrint=continuePrint;
return this;
}
public void printStringMethod() {
System.out.println(Thread.currentThread().getName() + "开始......");
while (continuePrint) {
}
System.out.println(Thread.currentThread().getName() + "结束......");
}
}
}
那么,如何才能使main线程修改完continuePrint变量的值之后,在子线程中也可以读取到呢?答案就是 使用 volatile 关键字。
Demo2(使用volatile,保证可见性)
首先说一下,volatile 关键字的作用就是使变量在多个线程之间是可见的!!!
在上个例子的基础上,我们将continuePrint变量的修饰符中添加上 volatile,那么这个变量对于main主线程、Thread-0子线程都是可见的了。也就是说,当Thread-0子线程执行到while(continuePrint){} 时,先执行一会这个空的循环体,然后main主线程将continuePrint变量修改为了false,这个时候,Thread-0子线程就可以读取到continuePrint变量被修改为了false,那么while循环就不成立了,自然而然的就结束了。
package com.szh.volatiletest;
/**
* volatile保证可见性
*/
public class Test01 {
public static void main(String[] args) {
PrintString ps=new PrintString();
//开启子线程,让子线程执行 ps 对象中的 printStringMethod() 方法
new Thread(new Runnable() {
@Override
public void run() {
ps.printStringMethod();
}
}).start();
//main线程睡眠1000ms
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("在main线程中修改打印标志");
/*
main线程在这里对 continuePrint 做了修改,但是子线程中是读取不到的
解决办法:使用 volatile 关键字修饰 continuePrint
volatile 关键字的作用可以强制线程从公共内存中读取数据,而不是从工作内存中读取
*/
ps.setContinuePrint(false);
}
static class PrintString {
private volatile boolean continuePrint=true;
public PrintString setContinuePrint(boolean continuePrint) {
this.continuePrint=continuePrint;
return this;
}
public void printStringMethod() {
System.out.println(Thread.currentThread().getName() + "开始......");
while (continuePrint) {
}
System.out.println(Thread.currentThread().getName() + "结束......");
}
}
}
Demo3(volatile不保证原子性)
这个例子演示volatile不保证原子性。
在main主线程中创建10个子线程,这10个子线程分别去执行1000次count++操作,那么根据简单的逻辑推理,这10个子线程的执行结果应该是类似于:1000,4000,8000,2000,......这样的,都是10的整数倍,但是从输出结果中看到,并非如此。
这里的原因可能有:①其中一个子线程的for循环还未执行完,另外的子线程就抢走了你的CPU执行权,另外的子线程开始执行它的for循环了,而此时的count变量因为有volatile修饰,所以count对其他子线程是可见的。
②其中一个子线程的count++操作执行到一半时,被另外的子线程抢走了CPU的执行权。(所以说这里的count++并不是原子性操作)
那么,Java中有两种方式实现原子性: 一种是使用锁; 另一种利用处理器的 CAS(Compare and Swap)指令。
锁具有排它性,保证共享变量在某一时刻只能被一个线程访问。CAS 指令直接在硬件(处理器和内存)层次上实现,看作是硬件锁。
package com.szh.volatiletest;
/**
* volatile不保证原子性
*/
public class Test02 {
public static void main(String[] args) {
//在main线程中创建10个子线程
for (int i = 0; i < 10; i++) {
new MyThread().start();
}
}
static class MyThread extends Thread {
public volatile static int count;
public static void addCount() {
for (int i = 0; i < 1000; i++) {
count++;
}
System.out.println(Thread.currentThread().getName() + " count = " + count);
}
@Override
public void run() {
addCount();
}
}
}
以上是关于Java——多线程高并发系列之volatile关键字的主要内容,如果未能解决你的问题,请参考以下文章
java多线程高并发学习从零开始——初识volatile关键字
Java——多线程高并发系列之synchronized关键字