volatile关键字修饰对象时注意事项

下面的代码实现无锁循环打印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中成功返回。


猜你喜欢

转载自blog.csdn.net/qq_30431719/article/details/80989762