多线程中共享对象的可见性

标签: 多线程 共享 对象 | 发表时间:2012-12-08 18:52 | 作者:
出处:http://www.iteye.com

在阅读《java并发编程实战》的第三章的时候,看到书中的一个例子,随在Eclipse中执行看看效果。示例代码如下:

 

public class NoVisibility {
	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来。其中打印42很好理解,因为,在启动ReaderThread线程以后,如果还没有设置ready为true,那么ReaderThread会一直循环,直到读取ready的值为true,然后打印出结果42来。

 

无限循环这个结果,通过代码来测试是很难观察到的,但是通过书中的分析,这种情况是存在的。这个示例中的number和ready,会被两个线程访问,其一是运行该程序的main线程,其二是ReaderThread线程,所以这两个变量可以称之为共享变量,由于java虚拟机自己的缓存机制,在缺少同步的情况下,会将number和ready的数值缓存在寄存器中,这就会导致ReaderThread线程在某些情况下读取不到最新的值,这样即使在main方法中将ready设置为true了,但是ReaderThread线程读取的ready值仍然为false,这样就会导致一直循环下去。

 

上面的这个示例很难观察到这种情况,所以我对其进行了改造,可以看到另外一种与我们预期不相符的情况:

 

public class NoVisibility {
	private static boolean ready;
	private static int number;
	
	private static class ReaderThread extends Thread {
		public void run() {
			while(!ready) {
				System.out.println(System.currentTimeMillis());
				Thread.yield();
			}
			
			System.out.println(number);
		}
	}
	
	public static void main(String[] args) {
		for(int i=0;i < 100;i++)
			new ReaderThread().start();
		
		number = 42;
		ready = true;
	}
}

  对示例代码做了两处改动,第一,在ReaderThread线程的while循环中打印当前时间,第二,在main方法中增加了一个循环,创建了100个ReaderThread线程并启动线程。然后我们看看运行结果:(这里只列出了部分结果)

 

 

...
1354960834108
42
1354960834108
1354960834108
42
1354960834108
42
1354960834108
1354960834108
1354960834108
1354960834108
42
42
...
 

 

从打印的结果来看,就会发现问题,为什么在某些线程打印了42以后,有些线程仍然在打印时间?

如果某个线程打印出了42,说明main方法已经执行完毕,即变量ready的值已经设置为true了,那么这以后其它的线程打印的结果应该都是42了,但这里的结果是有些线程读取的ready值仍然为false,这就说明了java虚拟机会对线程中使用到的变量进行缓存,所以就出问题了。

java虚拟机缓存变量,是出于性能的考虑,并且在单线程程序中,或者不存在共享变量的多线程程序中,这都不会出现问题。但是,在有共享变量的多线程程序中,就会发生问题,这里就涉及到共享对象的可见性了,也就是在没有使用同步机制的情况下,一个线程对某个共享对象的修改,并不会立即被其它的线程读取到。上面的代码之所以会出问题,就是因为ReaderThread线程,没有读取到main线程对ready变量修改后的值。要解决上述问题,可以通过在main方法和ReaderThread线程中的run方法中,给访问number和ready值的代码块中加锁来解决。

 

另外一种结果打印出0来,这个暂时还不是很明白,书中的解释是java虚拟机的内存模型允许编译器对操作顺序进行重排序,并将数值缓存在寄存器中,这样可能在读取到ready修改后的值后,却仍然读取了number的旧值,从而打印出了int的默认值0来。

 

 

 

 



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


ITeye推荐



相关 [多线程 共享 对象] 推荐:

多线程中共享对象的可见性

- - ITeye博客
在阅读《java并发编程实战》的第三章的时候,看到书中的一个例子,随在Eclipse中执行看看效果. 书中的解释是,这个程序的执行结果除了打印出42以外,还有两外另种情况:一时无限循环下去,二是打印出0来. 其中打印42很好理解,因为,在启动ReaderThread线程以后,如果还没有设置ready为true,那么ReaderThread会一直循环,直到读取ready的值为true,然后打印出结果42来.

多线程实现资源共享的问题学习与总结

- - 博客园_首页
我么知道Java传统多线程的实现有两种方法,继承Thread类或者实现Runnable即可.线程启动时调用start()方法.. 实现Runnable接口相比继承Thread类有如下好处:. 1.避免单继承的局限,一个类可以同时实现多个接口. 实现多线程模拟售票点卖票来说明实现Runnable即可可以达到资源共享的目的..

Java Thread多线程

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

共享成本

- wooden - 不许联想
如果再谈论什么音乐版权,主张听唱片不要听MP3,就会显得有些傻逼了. 之前我只要在博客上说,就会有一帮共享主义的捍卫者跳出来跟我理论. 甚至,IT界的人认为版权的概念该改改了——你直接说不该有版权不就完了,改个毛啊. 怎么改它都是一种权利,除非你消灭这种权利. 但我现在在思考这样一个问题,它是一个哲学问题,不是版权问题.

Java多线程之synchronized

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

[转]GDB调试多线程

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

java多线程总结

- - Java - 编程语言 - ITeye博客
在java中要想实现多线程,有两种手段,一种是继续Thread类,另外一种是实现Runable接口. 对于直接继承Thread的类来说,代码大致框架是:. class 类名 extends Thread{. * @author Rollen-Holt 继承Thread类,直接调用run方法.             System.out.println(name + "运行     " + i);.

[转]Oracle Parallel 多线程

- - Oracle - 数据库 - ITeye博客
对于一个大的任务,一般的做法是利用一个进程,串行的执行,如果系统资源足够,可以采用parallel技术,把一个大的任务分成若干个小的任务,同时启用n个进程/线程,并行的处理这些小的任务,这些并发的进程称为并行执行服务器(parallel executeion server),这些并发进程由一个称为并发协调进程的进程来管理.

Java多线程学习

- - CSDN博客编程语言推荐文章
  线程是一种轻量级的进程,它和进程一样拥有独立的执行控制,由操作系统负责调度,区别在于线程没有独立的存储空间,而是和所属进程中的其它线程共享一个存储空间,这使得线程间的通信远较进程简单. 即多个线程可以同时执行,就像有多条流水线一样,可以同时进行工作,是并发执行的.   程序是由进程组成的,进程是由线程组成的.

Java多线程(二)同步

- - CSDN博客编程语言推荐文章
如果你的java基础较弱,或者不大了解java多线程请先看这篇文章 java多线程(一)线程定义、状态和属性. 同步一直是java多线程的难点,在我们做android开发时也很少应用,但这并不是我们不熟悉同步的理由. 希望这篇文章能使更多的人能够了解并且应用java的同步. 在多线程的应用中,两个或者两个以上的线程需要共享对同一个数据的存取.