volatile具有内存可见性
来看下面这段代码:
import java.util.Scanner;
public class JAVA_THREAD06 {
static class Counter {
public int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
while (counter.flag == 0) {
// do nothing
}
System.out.println("循环结束!");
});
Thread t2 = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("输入一个整数:");
counter.flag = scanner.nextInt();
});
t1.start();
t2.start();
}
}
在输入的数值不是0时也不会结束循环,我们来解析一下这个过程。
这里存在两个线程,但由于线程t1和线程t2不在同步状态,所以当t2还没获取输入的数时,t1就已经判断结束了。
接下来我们验证一下这个猜想。将线程开始和结束进行打印来监视线程的运行顺序,将counter.flag进行打印监视它的值。
import java.util.Scanner;
public class JAVA_THREAD06 {
static class Counter {
public int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> { System.out.println("t1 start");
while (counter.flag == 0) {
// do nothing
}
System.out.println("循环结束!");
System.out.println("t1 finish");
});
Thread t2 = new Thread(() -> {
System.out.println("t2 start");
Scanner scanner = new Scanner(System.in);
System.out.println("输入一个整数:");
counter.flag = scanner.nextInt();
System.out.println("t2 finish");
});
t1.start();
t2.start();
System.out.println(counter.flag);
}
}
这里主线程就直接运行,将counter.flag的值给打印出来了,然后两个线程才会运行。在输入数值2后线程t2停止运行,线程t1没有反应,判断要么不能读取到counter.flag的值、要么就是已经读取了并且判断结束(判断结果是true所以会不停循环)。
这里再验证新的猜想,把flag的初始值设为非0.
static class Counter {
public int flag = 2;
}
结果显然符合线程t1提前进行判断并结束的猜想。
那么这里线程t1会等不到flag的变化的情况就叫做不可见。即线程t2对于flag的改变t1看不到了。
那么想要让flag数值的修改可见,线程t1能够读取到输入的数值我们应该怎么办呢?这就需要使用我们今天的主角——volatile关键字。
接下来我们对之前的代码进行如下改变:
在两个线程共享的变量前加上volatile关键字
static class Counter {
public volatile int flag = 0;
}
运行结果如下:
volatile不具有原子性
这里就可以谈谈synchronized和volatile的本质区别:synchronized绝对保证原子性,volatile绝对保证可见性。
static class Counter {
public volatile int flag = 0;
void increase(){
this.flag++;
}
}
public static void main(String[] args) throws InterruptedException{
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for(int i=0;i<10000;i++){
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for(int i=0;i<10000;i++){
counter.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(counter.flag);
}
从这里的运行结果可以看出volatile并不保证原子性。
synchronized也具有可见性
用同一个例子来证明
static class Counter {
public int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
// System.out.println("t1 start");
while (true) {
synchronized (counter){
if(counter.flag!=0)break;
}
// do nothing
}
System.out.println("循环结束!");
// System.out.println("t1 finish");
});
Thread t2 = new Thread(() -> {
// System.out.println("t2 start");
Scanner scanner = new Scanner(System.in);
System.out.println("输入一个整数:");
counter.flag = scanner.nextInt();
// System.out.println("t2 finish");
});
t1.start();
t2.start();
// System.out.println(counter.flag);
}
注意:这里的synchronized是对代码块使用,加锁的对象是已经实例化好了的counter对象。