下面的代码实现无锁循环打印A,B,C的功能,但是volatile关键字用错的话,会导致数据不打印
1.当volatile修饰公共的成员变量时
public class ThreadTest {
private static volatile C c = new C();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
if(c.x == 'A') {
System.out.println('A');
c.x = 'B';
}else {
//System.out.println("aaaaaa===="+c.x);
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
if(c.x == 'B') {
System.out.println('B');
c.x = 'C';
}else {
//System.out.println("bbbbbbbb===="+c.x);
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
while(true) {
if(c.x == 'C') {
System.out.println('C');
c.x = 'A';
}else {
//System.out.println("ccccccc===="+c.x);
}
}
}
}).start();
}
}
class C{
public char x = 'A';
}
这段代码执行是没有问题的,因为c是volatile类型的,虽然每个线程的缓存都保存了c的副本,但是没次改变x的值后,c都会刷新到主存中,并且让其他线程缓存的c失效。如果不使用volatile修饰,那么线程会使用自己本地缓存的c,这样会导致while循环内部的判断不生效,不打印数据
2.volatile修饰公共的成员变量,但是线程不使用
public class ThreadTest2 {
private static volatile B b = new B();
public static void main(String[] args) {
new T(b, 'A', 'B').start();
new T(b, 'B', 'C').start();
new T(b, 'C', 'A').start();
}
}
class B{
public char x = 'A';
}
class T extends Thread{
public T(B b,char sysChar,char nextChar) {
this.b = b;
this.sysChar = sysChar;
this.nextChar = nextChar;
}
public B b;
public char sysChar;
public char nextChar;
@Override
public void run() {
while(true) {
if(b.x == sysChar) {
System.out.println(sysChar);
b.x = nextChar;
//System.out.println(b);
}else {
//System.out.println("ccccccc===="+c.x);
}
}
}
}
此时,虽然b是volatile修饰的,而且T里面的B类也是用b初始化的,此时四个引用指向的都是b对象,但是,线程内部使用的还是缓存中的b对象,由于T类中的b不是volatile类型,所以不会刷新主存,也不会使其他线程的缓存失效。但是,如果将T中的b改成volatile,或者将B类中的x改成volatile,都会刷新主存,代码可以成功执行。
3.总结
直接使用volatile修饰的引用,操作对象的话,对象值的任何改变都是线程可见的,都会刷新主存,无效其他线程的缓存。具体可以参考volatile是否能保证数组中元素的可见性。
4.happens-before原则
程序顺序规则:一个线程中的每个操作,happens- before 于该线程中的任意后续操作。
监视器锁规则:对一个监视器锁的解锁,happens- before 于随后对这个监视器锁的加锁。
volatile变量规则:对一个volatile域的写,happens- before 于任意后续对这个volatile域的读。
传递性:如果A happens- before B,且B happens- before C,那么A happens- before C。
Thread.start()的调用会happens-before于启动线程里面的动作。
Thread中的所有动作都happens-before于其他线程从Thread.join中成功返回。