欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

线程安全性-volatile

程序员文章站 2024-01-01 14:27:16
...

今晚读了新同事关于P2P做种的Java 代码,Code风格很漂亮,但仔细多看几眼,就会出现瑕疵。其中有一点关于线程的安全性,还是觉得有必要拿出来说说。

代码的意图是利用一个标识性Boolean变量来控制一个线程的开启(Service)与结束(Shutdown)。
A类:
private Boolean isStarted = false;

public void start () {
	isStarted = true;
	while(isStarted){
		// do something
}
}

public void stop() {
	isStarted = false;
}


看完上段代码,不少朋友应该知道问题出现在了什么地方。没错,A类是线程不安全的,其他线程对isStarted 变量的修改,对于A线程类来说,会发生不可预知的问题。

对象的可见性
在一个单线程化的环境里,如果向一个变量先写入值,然后在没有写干涉的情况下读取这个变量,那么会得到预料之中的相同返回值。但是当读和写发生在不同的线程中时,通常不能保证读线程及时地读取其他线程写入的值,甚至可以说根本不可能。为了确保跨线程写入的内存可见性,需使用同步机制。
private static boolean ready;
	private static int number;

	private static class ReaderThread extends Thread
	{
		public void run() {
			while(!ready)
			Thread.yield();
			System.out.println(number);
		}
	}

	public static void main(String[] args){
		new ReaderThread().start();
		number = 42;
		ready = true;
	}


上段代码看起来最终执行的结果会是输出42,但事实上,它很有可能会输出0,甚至永远不会终止。这是因为没有使用恰当的同步机制。也就是说没能保证number和ready 的值对读线程是可见的。

很奇怪吧,但事实确实如此,因为对于读线程来说,ready 的值可能永远不可见,甚至会输出0,因为在对number赋值之前,主线程就已经写入ready,并对读线程写见。Okay,这是一种“重排序”(在单线程中,只要重排序不会对结果产生影响,那就不能完全保证其中的操作一定是按照程序所写的那样。 这是JVM的编译器为了充分利用多核处理器性能而设计的,这方面不了解的朋友可以了解一下Java存储模型)。

这种情况在我的项目中发生,是非常的可怕的。幸运的是被发现了,且Java提供了一个简单的方法来避免这个问题。

Volatile变量

Java提供了一种同步的弱形式(这也是一种以锁的形式出现的):volatile变量。它确保对一个变量的更新以可预见的方式通知其他的线程。当一个域声明为volatile类型后,编译器和运行时会监视这个变量:它是共享的,而且对它的操作不会与其他的内存操作一起被重排序。
volatile boolean asleep;
	...
		while (!asleep)
		{
			countSomeSheep();
		}

一个典型的volatile变量应用:检查状态标记,以确定是否退出一个循环。为了保证执行检查的线程能够注意到asleep已被其他线程修改。Asleep标记就必须为volatile。Okay, 这里同样可以使用锁来代替volatile,一样能保证对asleep变量修改的可见性。

最后说说volatile与锁的区别:
加锁可以保证可见性与原子性,然而volatile变量只能保证可见性
相关标签: JVM thread

上一篇:

下一篇: