说volatile之前先要了解主内存,工作内存
对于多线程来说,注意三个特性:1.原子性,2.有序性,3.可见性,这里不讲述
volatile规则:
- 保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
- 行指令重排序。
- 保证有序性,可见性,不保证原子性
使用volatile关键字会强制将修改的值立即写入主存,如当线程2进行修改时,会导致线程1的工作内存中缓存变量无效,由于线程1的工作内存中缓存变量无效所以会再次去主内存中去取
如此,我们可以写一段代码验证一下
public static void main(String[] args) {
new Thread(){
Test test = new Test();
public void run(){
test.test1();
}
}.start();
new Thread(){
public void run(){
test.test2();
}
}.start();
}
int a = 0;
public void test1(){
String name = Thread.currentThread().getName();
System.out.println(name + " start");
a = a+1;
System.out.println(name+" a1--"+ a);
}
public void test2(){
String name = Thread.currentThread().getName();
System.out.println(name+" a1 -- " + a);
}
可以看到,我起了两个线程分别调用test1对a做自增,test2打印,结果是什么呢?
Thread-0 start
Thread-0 a1--1
Thread-1 a1 -- 0
Thread-0 start
Thread-0 a1--1
Thread-1 a1 -- 1
两种情况都可能,也就是说如果test1计算后将值写入主内存后test2才执行,这时是第二种从主内存中得到的是更改后的
如果test1计算后还没来得及由工作内存写入主内存test2就开始执行了,从主内存中获取的是更改前的值后,test1才将值更新到主内存中,这就是第一种,就有问题了
而用volatile修饰变量a就可以解决这个问题,结果就是第二种
一开始偶然间犯了一个小错误,从而变得更有意思些
public static void main(String[] args) {
new Thread(){
public void run(){
new Test().test1();
}
}.start();
new Thread(){
public void run(){
new Test().test2();
}
}.start();
}
volatile int a = 0;
public void test1(){
String name = Thread.currentThread().getName();
System.out.println(name + " start");
a = a+1;
System.out.println(name+" a1--"+ a);
}
public void test2(){
String name = Thread.currentThread().getName();
System.out.println(name+" a1 -- " + a);
}
看下这样的运行结果是什么呢?
Thread-0 start
Thread-0 a1--1
Thread-1 a1 -- 0
虽然有volatile修饰,但是Thread-1得到的还是0,这是为什么呢?后来找了找原因才看到,虽然起了两个线程,但是也是起了两个test实例,每个线程分别new了一个实例,那么获取的也是不同的内存,不同的副本,这个怎么改一下呢?
要么改成最上面那样new一个实例进行调用
要么就加上static 即:static volatile int a = 0 ,这样共享一个实例就可以了
还有一个问题,volatile不保证原子性怎么办?,可以看下这个
volatile int a = 0;
public static void main(String[] args) {
final Test test = new Test();
new Thread(){
public void run(){
test.test1();
}
}.start();
new Thread(){
public void run(){
test.test2();
}
}.start();
}
public void test1(){
String name = Thread.currentThread().getName();
System.out.println(name + " start-" + a);
int b = a;
System.out.println(name + " b--"+ b);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
a = b +1;
System.out.println(name+" a1--"+ a);
}
public void test2(){
String name = Thread.currentThread().getName();
a = a+1;
System.out.println(name+" a1 -- " + a);
}
这个运行是什么?
Thread-0 start-0
Thread-0 b--0
Thread-1 a1 -- 1
Thread-0 a1--1
为什么是这个结果?自增了两次,那么Thread-0和Thread-1结果应该是1和2啊,因为他们同时从主内存中获取a为0,test1()已经声明变量b也指向a的值0,然后等待,这时test2()执行+1操作,写入主内存为1,test1()睡醒b+1也为1写入a的主内存,那么就绕过了volatile的特性
解决办法:synchronized,对同一变量+1操作采取同步
Lock lock = new ReentrantLock();两者具体怎么用就不说了
基础知识还是得狠狠补一下的啊,要想质的飞跃,就得脚踏实地
网友评论