java并发编程实战之理解同步的可见性

版权声明:转载注明出处 https://blog.csdn.net/nobody_1/article/details/82767816

同步机制可以保证“读取-修改-写入”复合操作的原子性,从而保证线程安全。同步机制其实还有一个特性:可见性

何为可见性?

看下面一段代码:

public class NoVisibility {
	private static boolean ready;
	private static int number;
	
	private static class ReadThread extends Thread{
		public void run(){
			while (!ready) {
				Thread.yield();
			}
			System.out.println(number);
		}
	}
	
	public static void main(String[] args){
		new ReadThread().start();
		number = 32;
		ready = true;
	}
}

代码大意:主线程和ReadThread线程共享变量ready和变量number,主线程修改变量的值,ReadThread线程根据ready值判断线程是否阻塞或者打印number的值。程序运行的结果有很多种:①可能会打印出32;②可能会打印出0;③可能被阻塞进而无打印。出现多种结果原因有:主线程和读线程没有使用同步,导致两个线程在读取变量ready和变量number的值处于未知的状态,可以简单的理解主线程对变量的修改对读线程不可见(其实是因为线程执行的无序性,即重排序),通过这个例子,可以很好的理解可见性。

同步机制如何实现可见性?

可见性是同步机制的一个属性,即实现同步的方法或者代码块对共享变量的修改对其他线程(前提是获得同一个锁的线程们)是可见的。
内置锁实现可见性机制
用内置锁实现的同步机制,当线程B执行由锁保护的同步代码块时,可以看到线程A之前在同一个同步代码块中的所有操作结果。注意是同一个内置锁,不同内置锁则无法保证同步机制。

volatile变量有什么用?

有些面试官喜欢把volatile和synchronized关键字放在一起问面试者两者的区别。虽然内置锁(synchronized)已经保证原子性操作和内存可见性,但java语言还提供了一种稍弱的同步机制,也就是volatile变量。那么可以理解,volatile和synchronized都可以实现同步机制,只是volatile变量的同步性更弱,更弱则表现在无法保证原子操作(见下面实例代码)。由于在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,故而volatile变量是一种比synchronized关键字更轻量级的同步机制
实例代码:volatile不能保证i++的原子操作

public class VolatileTest {
	static volatile int i = 0; 
	private static class PlusTask implements Runnable{
		
		public void run(){
			for (int k =0; k < 10000; k++){
				i++;
			}
		}
	}
	
	public static void main(String[] args) throws InterruptedException{
		Thread[] threads = new Thread[10];
		for (int j = 0; j < threads.length; j++) {
			threads[j] = new Thread(new PlusTask());
			threads[j].start();
		}
		
		for (int m = 0; m < threads.length; m++) {
			threads[m].join();
		}
		
		System.out.println(i);
	}
}

注:实例来源于《实战java高并发程序设计》

volatile变量如何保证可见性?

volatile变量的主要作用:用来确保将变量的更新操作通知其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存到寄存器或者对其他线程不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。

synchronized VS volatile?

因为加锁机制实现的同步性既可以保证内存可见性又能保证原子操作,而volatile变量只能保证原子性,所以为了保证线程安全更倾向于使用synchronized。
当且仅当满足以下所有条件时,才应该使用变量:
① 对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值;
② 该变量不会与其他状态变量一起纳入不变形条件中;
③ 在访问变量时不需要加锁。
可见,使用volatile变量的条件非常的苛刻。
对于开篇的实例,只需要对ready和number使用volatile关键字修饰,就能解决变量不可见的问题。

参考资料:
《实战java高并发程序设计》2.3节
《java并发编程实战》3.1节

猜你喜欢

转载自blog.csdn.net/nobody_1/article/details/82767816
今日推荐