线程之间的可见性

标签: 线程 | 发表时间:2013-02-13 17:31 | 作者:
出处:http://www.iteye.com

以服务器模式运行下面的Java程序: ( 默认为client模式) (本机使用的是Oracle的Hotspot VM)

java -server StopThread

 

import java.util.concurrent.TimeUnit;

// Broken! - How long would you expect this program to run ?
public class StopThread {

	private static boolean stopRequested;  // value: false
	
	public static void main(String... args) throws InterruptedException {
		
		Thread backgroundThread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				int i = 0;
				while(!stopRequested)
					i++;
			}
		});
		
		backgroundThread.start();
		
		TimeUnit.SECONDS.sleep(1);
		stopRequested = true;
	}
}

 这个例子有两个线程,一个是main线程,另一个是在main线程里启动的backgroundThread线程,这两个线程共享一个类型为boolean的stopRequested。理论上如果main线程将stopRequested变量设置为true时,backgroundThread线程会跳出循环然后结束,接着JVM也终止。但是在我的机子上(Intel i5 M460),它一直处于运行中,并不会在1秒中后停止运行。

原因要归咎于Java语言规范(JLS)中的内存模型(Memory Model)。它规定了一个线程所做的变化何时以及如何变成对其它线程可见(可见性)。因此我们不知道何时后台线程能够“看到”main线程对stopRequested的值所做的改变。一个简洁,性能好的方案是对共享变量添加volatile关键字。volatile保证任何一个线程在读取该域的时候都能“看到”最近刚刚被写入的值。

修改后的代码:

import java.util.concurrent.TimeUnit;

// Broken! - How long would you expect this program to run ?
public class StopThread {

	private static boolean volatile stopRequested;  // value: false
	
	public static void main(String... args) throws InterruptedException {
		
		Thread backgroundThread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				int i = 0;
				while(!stopRequested)
					i++;
			}
		});
		
		backgroundThread.start();
		
		TimeUnit.SECONDS.sleep(1);
		stopRequested = true;
	}
}

 以服务器模式运行后会在1秒左右后自动停止VM。

 

除了使用关键字volatile,还可以使用synchronized。同步机制除了能够实现线程之间的互斥之外,还能实现线程间的通信(即可以实现可见性)。

import java.util.concurrent.TimeUnit;

// Broken! - How long would you expect this program to run ?
public class StopThread {

	private static boolean stopRequested;  // value: false
	
	public static synchronized void requestStop() {
		stopRequested = true;
	}
	public static synchronized boolean stopRequested() {
		return stopRequested;
	}
	public static void main(String... args) throws InterruptedException {
		
		Thread backgroundThread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				int i = 0;
				while(!stopRequested())
					i++;
			}
		});
		
		backgroundThread.start();
		
		TimeUnit.SECONDS.sleep(1);
		requestStop();
	}
}

 此时可以且应该取消volatile关键字,因为synchronized关键字也能实现可见性。

另外需要注意的是除了同步写操作之外,还应该同步读操作。如果读和写操作没有都被同步,同步就不会起作用。



已有 0 人发表留言,猛击->> 这里<<-参与讨论


ITeye推荐



相关 [线程] 推荐:

MySQL Replication 线程

- - CSDN博客推荐文章
Replication 线程. Mysql 的Replication 是一个异步的复制过程,从一个Mysql instace(我们称之为Master)复制到另一个Mysql instance(我们称之Slave). 在Master 与Slave 之间的实现整个复制过程主. 要由三个线程来完成,其中两个线程(Sql 线程和IO 线程)在Slave 端,另外一个线程(IO 线程)在Master 端.

Java线程池

- - 企业架构 - ITeye博客
线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的. 在jdk1.5之后这一情况有了很大的改观. Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用. 为我们在开发中处理线程的问题提供了非常大的帮助.

Java 线程池

- - 编程语言 - ITeye博客
在项目中,系统启动一个新线程的成本是比较高的,因为它涉及与操作系统交互. 在这种情形下,使用线程池可以很好地提高性能,尤其是当程序中需要创建大量生存周期很短的线程时,更应该考虑使用线程池. 使用线程池可以有效地控制系统中并发线程的数量,当系统中包含大量并发线程时,会导致系统性能剧烈下降,甚至导致JVM崩溃,而线程池的最大线程数参数可以控制系统中并发线程数不超过此数.

Java线程(四):线程中断、线程让步、线程睡眠、线程合并

- - 编程语言 - ITeye博客
 最近在Review线程专栏,修改了诸多之前描述不够严谨的地方,凡是带有Review标记的文章都是修改过了. 本篇文章是插进来的,因为原来没有写,现在来看传统线程描述的不太完整,所以就补上了. 理解了线程同步和线程通信之后,再来看本文的知识点就会简单的多了,本文是做为传统线程知识点的一个补充. 有人会问:JDK5之后有了更完善的处理多线程问题的类(并发包),我们还需要去了解传统线程吗.

Java Thread多线程

- - CSDN博客推荐文章
Java Thread多线程. Java 多线程例子1 小例子. super("zhuyong");//设置线程的名字,默认为“TestThread”. Java 多线程例子2 前台线程(用户线程) 后台线程(守护线程 ). 1,setDaemon(true)后就是后台线程(守护线程 ),反之就是前台线程(用户线程).

Android线程大坑

- - 移动开发 - ITeye博客
     android界面的更新实在主线程进行的,通常把主线程也叫UI线程,UI线程里进行事件的分发和交互. 在UI线程中进行耗时操作,比如网络请求,IO操作等会阻塞UI线程,界面会卡住,并且超过大概5秒钟程序会ANR(Application Not Responding),也就是死掉. 其实这种GUI单线程的思想在我上一篇博客(http://zyqwst.iteye.com/blog/2262011)都有阐述,道理一模一样,只是android实现的方式上略有不同,所以我建议把上一篇Swing线程的博客能够阅读一遍,Android线程的问题豁然开朗,始终晋级GUI开发的原则:在UI线程中进行界面的更新操作,在单独线程中进行耗时操作.

浅谈 iOS 线程

- - SegmentFault 最新的文章
通常主线程和其他线程的使用场景. Tips: 解压、打开 Zip 包,读写较大文件的操作也不宜放在主线程里. 一般异步网络请求中会有一个 completionBlock ,这个 completionBlock 是在主线程中被调用的. 所以,可能消耗大量时间的代码(例如上面提到的处理 Zip 包的方法)也不宜放在这些 block 中.

Java线程之FutureTask

- - zzm
FutureTask是Future和Callable的结合体. 然后通过Future来取得计算结果. 但是,若开启了多个任务,我们无从知晓哪个任务最先结束,因此,若要实现“当某任务结束时,立刻做一些事情,例如记录日志”这一功能,就需要写一些额外的代码. FutureTask正是为此而存在,他有一个回调函数protected void done(),当任务结束时,该回调函数会被触发.

Java多线程之synchronized

- - CSDN博客推荐文章
这里通过三个测试类阐述了synchronized应用的不同场景. 首先是最基本的synchronized Method的使用.  * @see 概述:Java中的每个对象都有一个锁(lock)或者叫做监视器(monitor) .  * @see 说明:当synchronized关键字修饰一个方法时,则该方法为同步方法 .

[转]GDB调试多线程

- - 小彰
GDB 多线程调试基本命令 实现简介 以及一个问题的解决. 一直对GDB多线程调试接触不多,最近因为工作有了一些接触,简单作点记录吧. 先介绍一下GDB多线程调试的基本命令. 显示当前可调试的所有线程,每个线程会有一个GDB为其分配的ID,后面操作线程的时候会用到这个ID. 切换当前调试的线程为指定ID的线程.