1. 简介
为了保证线程的安全性问题,Java语言提供了两种关键字volatile 和 synchronized关键字。
-
volatile关键字保证:可见性,有序性(禁止指令重排序)。可以
-
synchronized关键字保证:可见性,原子性,有序性。
其中volatile并没有解决原子性的问题,对于一些原子操作volatile修饰的变量还是无法保证线程的安全性问题,这时就必须使用synchronized关键字或者其他同步手段。
2. 可见性
- 先来看一下多线程的工作模型,线程工作时每个线程都有各自的工作缓存区,当线程需要对变量操纵时,先从主内存中将数据读到各自的缓存中,再将缓存中的数据调入线程主体完成对变量的操作,然后将数据写入到缓存中,最后回写到主内存中。由于线程之间的并发是存有不确定因素的,可能线程1读取到缓存1之后,线程2也将数据读到缓存2中并且完成了对数据的修改回写到主内存中,此时缓存1中的数据就是一个脏数据,并没有什么用。这时就需要一种手段,线程1修改完数据线程2能立马知道这就是可见性。
class RunThread1 extends Thread{
public boolean is = true;
@Override
public void run() {
System.out.println("进入run方法!");
while(is){ }
System.out.println("线程被停止了!");
}
}
public class Demo01 {
public static void main(String[] args) {
try{
RunThread1 r = new RunThread1();
r.start();
Thread.sleep(1000);
r.is = false;
System.out.println("赋值为false!");
} catch (Exception e){
e.printStackTrace();
}
}
}
注意箭头,程序并没有终止运行。
- volatile修饰的变量,具有可见性。强制线程主体直接从主内存中读取数据。越过各自的缓存,这样就能保证每次拿到的都是最新的数据。
class RunThread2 extends Thread{
volatile public boolean is = true;
@Override
public void run() {
System.out.println("进入run方法!");
while(is){
}
System.out.println("线程被停止了!");
}
}
public class Demo02 {
public static void main(String[] args) {
try {
RunThread2 r = new RunThread2();
r.start();
Thread.sleep(1000);
r.is = false;
System.out.println("赋值为false!");
} catch (Exception e) {
e.printStackTrace();
}
}
}
3. 原子性
volatile并没有解决原子性的问题,什么是原子性?
原子:原子是化学反应不可再分的最小微粒,也是构成一般物质的最小单位。
原子性:具有不可再分割性。比如常见的变量操作 i++ 其实是分为3步曲,从内存中取值,i = i + 1,然后回写到内存中,至少需要经历三个步骤。在多线程并发状态下volatile 修饰的变量是无法保证线程安全的。i = i + 1有可能多个线程同时操作,一旦一个线程完成了修改其他线程拿到的依然是脏数据。这时需要synchronized同步 或者 使用volatile + Atomic类来保证了,Atomic类的虽然能够完成但是计算的中间过程是不对的。
- 只使用volatile修饰变量。
class MyRun1 implements Runnable{
volatile int a = 0;
@Override
public void run() {
for(int i = 0;i < 1000;i++){
a++;
}
}
}
public class Demo01 {
public static void main(String[] args) {
try {
MyRun1 r = new MyRun1();
Thread [] a = new Thread[10];
for(int i = 0;i < a.length;i++){
a[i] = new Thread(r);
a[i].start();
}
Thread.sleep(2000);
System.out.println(r.a);
} catch (Exception e){
e.printStackTrace();
}
}
}
- 使用volatile修饰变量 + AtomicInteger类。
import java.util.concurrent.atomic.*;
class MyRun2 implements Runnable{
volatile AtomicInteger a = new AtomicInteger(0);
@Override
public void run() {
for(int i = 0;i < 1000;i++){
a.getAndIncrement();
}
System.out.println(Thread.currentThread().getName() + "线程加完100后的值: " + a.toString());
}
}
public class Demo02 {
public static void main(String[] args) {
try {
MyRun2 r = new MyRun2();
Thread [] a = new Thread[10];
for(int i = 0;i < a.length;i++){
a[i] = new Thread(r);
a[i].start();
}
Thread.sleep(2000);
System.out.println(r.a);
} catch (Exception e){
e.printStackTrace();
}
}
}
4. 有序性(禁止指令重排序)
- 重排序:在Java程序运行的时候,即时编译器对没有拓扑依赖关系的指令,动态的改变程序代码的运行的顺序。可以提高运行的效率。
当然也有可能BD互换,AC互换也是有可能的。
public class Demo01 {
private static int x = 0;
private static int y = 0;
private static int a = 0;
private static int b = 0;
private static int c = 0;
private static int d = 0;
private static int e = 0;
private static int f = 0;
private static int cnt = 0;
public static void main(String[] args) throws InterruptedException {
while(true){
x = y = a = b = c = d = e = f = 0;
cnt++;
Thread t1 = new Thread( () -> {
a = 1;
c = 5;
d = 8;
x = b;
});
Thread t2 = new Thread( () -> {
b = 1;
e = 8;
f = 7;
y = a;
});
t1.start();
t2.start();
t1.join();
t2.join();
String s = "cnt = " + cnt + ", x = " + x + ", y = " + y;
System.out.println(s);
if(x == 0 && y == 0){
break;
}
}
}
}
- volatile关键字解决了有序性的问题可以禁止代码指令的重排序,当然并不是指所有的指令重排序。而是以volatile关键字为界限,上界没有拓扑依赖的指令可以重排序,下界没有拓扑依赖的指令也可以重排序。
public class Demo02 {
private static int x = 0;
private static int y = 0;
private static int a = 0;
private static int b = 0;
volatile private static int c = 0;
private static int d = 0;
private static int e = 0;
private static int f = 0;
private static int cnt = 0;
public static void main(String[] args) throws InterruptedException {
while(true){
x = y = a = b = c = d = e = f = 0;
cnt++;
Thread t1 = new Thread( () -> {
c = 7;
a = 1;
d = 8;
x = b;
});
Thread t2 = new Thread( () -> {
e = d;
b = 1;
f = d;
y = a;
});
t1.start();
t2.start();
t1.join();
t2.join();
String s = "cnt = " + cnt + ", x = " + x + ", y = " + y + ", e = " + e + ", f = " + f;
System.out.println(s);
if(x == 0 && y == 0 && e == 0 && f == 0){
break;
}
}
}
}
5. 总结
-
可见性:volatile关键字 和 synchronized关键字都实现了可见性。
-
原子性:只有synchronized实现了原子性
-
禁止指令重排序:volatile关键字 和 synchronized关键字保证了有序性。
-
volatile关键字是轻量级的同步,synchronized关键字重量级的同步。