破除Java神话之三

java中原子操作是线程安全的论调经常被提到。根据定义,原子操作是不会被打断的操作,因此被认为是线程安全的。实际上有一些原子操作不一定是线程安全的。

这个问题出现的原因是尽量减少在代码中同步关键字。同步会损害性能,虽然这个损失因JVM不同而不同。另外,在现代的JVM中,同步的性能正在逐步提高。尽管如此,使用同步仍然是有性能代价的,并且程序员永远会尽力提高他们的代码的效率,因此这个问题就延续了下来。

在java中,32位或者更少位数的赋值是原子的。在一个32位的硬件平台上,除了double和long型的其它原始类型通常都是使用32位进行表示,而double和long通常使用64位表示。另外,对象引用使用本机指针实现,通常也是32位的。对这些32位的类型的操作是原子的。

这些原始类型通常使用32位或者64位表示,这又引入了另一个小小的神话:原始类型的大小是由语言保证的。这是不对的。java语言保证的是原始类型的表数范围而非JVM中的存储大小。因此,int型总是有相同的表数范围。在一个JVM上可能使用32位实现,而在另一个JVM上可能是64位的。在此再次强调:在所有平台上被保证的是表数范围,32位以及更小的值的操作是原子的。

那么,原子操作在什么情况下不是线程安全的?主要的一点是他们也许确实是线程安全的,但是这没有被保证!java线程允许线程在自己的内存区保存变量的副本。允许线程使用本地的私有拷贝进行工作而非每次都使用主存的值是为了提高性能。考虑下面的类:

class RealTimeClock
{
private int clkID;
public int clockID()
{
return clkID;
}
public void setClockID(int id)
{
clkID = id;
}
//...
}

现在考虑RealTimeClock的一个实例以及两个线程同时调用setClockID和clockID,并发生以下的事件序列:

T1 调用setClockID(5)

T1将5放入自己的私有工作内存

T2调用setClockID(10)

T2将10放入自己的私有工作内存

T1调用clockID,它返回5

5是从T1的私有工作内存返回的

对clockI的调用应该返回10,因为这是被T2设置的,然而返回的是5,因为读写操作是对私有工作内存的而非主存。赋值操作当然是原子的,但是因为JVM允许这种行为,因此线程安全不是一定的,同时,JVM的这种行为也不是被保证的。

两个线程拥有自己的私有拷贝而不和主存一致。如果这种行为出现,那么私有本机变量和主存一致必须在以下两个条件下:

1、变量使用volatile声明

2、被访问的变量处于同步方法或者同步块中

如果变量被声明为volatile,在每次访问时都会和主存一致。这个一致性是由java语言保证的,并且是原子的,即使是64位的值。(注意很多JVM没有正确的实现volatile关键字。你可以在www.javasoft.com找到更多的信息。)另外,如果变量在同步方法或者同步块中被访问,当在方法或者块的入口处获得锁以及方法或者块退出时释放锁是变量被同步。

使用任何一种方法都可以保证ClockID返回10,也就是正确的值。变量访问的频度不同则你的选择的性能不同。如果你更新很多变量,那么使用volatile可能比使用同步更慢。记住,如果变量被声明为volatile,那么在每次访问时都会和主存一致。与此对照,使用同步时,变量只在获得锁和释放锁的时候和主存一致。但是同步使得代码有较少的并发性。

如果你更新很多变量并且不想有每次访问都和主存进行同步的损失或者你因为其它的原因想排除并发性时可以考虑使用同步。

时间: 2025-01-28 03:43:51

破除Java神话之三的相关文章

破除java神话之线程按优先级唤醒

在编写多线程代码的时候经常发生多个线程等待一个事件的情况.这种情况多发生于多个线程在同步方法或者同步块内调用wait方法等待同一个被锁住的对象.当另一个锁住该对象的线程从同步方法或者同步块中调用notify或者notifyAll方法时这些等待线程被唤醒.notify调用仅仅唤醒一个线程,因此如果有多个线程正处于等待状态,那么不会有对锁的竞争.另一方面,notifyAll调用唤醒所有的等待线程而造成竞争,然而只有一个线程能够得到锁,其它的都会被阻塞. 当多个线程处于等待状态时的问题是当调用noti

破除Java神话之参数按传址方式传递

在不同的java新闻组中,参数是传值还是传址一直是一个经常被争辩的话题.误解的中心是以下两个事实: 对象是传引用的 参数是传值的 这两个能够同时成立吗?一个字:是!在java中,你从来没有传递对象,你传递的仅仅是对象的引用!一句话,java是传引用的.然而,当你传递一个参数,那么只有一种参数传递机制:传值! 通常,当程序员讨论传值和传引用时,他们是指语言的参数传递机制,c++同时支持这两种机制,因此,以前使用过c++的程序员开始好像不能确定的java是如何传参数的.java语言为了事情变得简单只

破除Java神话之一

对于java程序员而言,垃圾收集功能是一个非常大的帮助,同时也是使用java语言的一个非常大的优势. 然而,实际情况应该是不能因为垃圾收集可以清除无用的内存就不去考虑内存问题.这里要指明的是, 如果忽略这个问题,那么就会导致问题. 首先,在不同的JVM上垃圾收集算法是不同的,因此,如果你想你的程序能够很好的运行在不同的JVM上,那么就不能依赖垃圾收集的特定行为.垃圾收集是一个非常活跃的研究问题,更好.更快并且更精确的收集器总在实现中. 然而很多现代的垃圾收集器都有着同样的问题.其中一个是当他们运

博客的神话,真实的谎言

博客圈流传诸多博客的神话,即所谓的博客成功之道,但其中绝大多数是那种如果你遵循而行之后会导致新博客以死告终的真实的谎言.然后,许多新博主却沉迷在这些博客神话中不可自拔,他们不但深信不疑并且还严格地执行,因为这些博客之道可能是他们从一些令人尊敬和钦佩的老博主那里听来,但按部就班的结果却是让他们在挫折和迷茫之中选择了关闭博客,因为他们从来没有从坚持这些博客之道的努力之中取得任何用用的结果. 笔者(注:Onibalusi Bamidele,Young Entrepreneur Blog的博主)有一个还

该学Java或.NET?

自从.NET问世以来,程序员都很关心的一个问题是「该学Java或.NET」.我也在挣扎,该「该继续Java的研究,或者该开始准备培养.NET的知识」. 当然,最好是能两者兼顾,但是每个人的时间都很有限,想要兼顾两者,其实不太容易.投入在.NET的时间越多,所能花费在Java的时间自然就少了,反之亦然.在信息爆炸的时代,重要的不是信息的取得,而是信息的抉择.信息太多,时间太少,如果不能慎选适合的技术,只会平白浪费许多时间,斫丧自己的竞争力. 由于我喜新厌旧的个性使然,过去这两年半,我着实花了不少时

博客的神话 真实的谎言

博客圈流传诸多博客的神话,即所谓的博客成功之道,但其中绝大多数是那种如果你遵循而行之后会导致新博客以死告终的真实的谎言.然后,许多新博主却沉迷在这些博客神话中不可自拔,他们不但深信不疑并且还严格地执行,因为这些博客之道可能是他们从一些令人尊敬和钦佩的老博主那里听来,但按部就班的结果却是让他们在挫折和迷茫之中选择了关闭博客,因为他们从来没有从坚持这些博客之道的努力之中取得任何用用的结果. 笔者(注:Onibalusi Bamidele,Young Entrepreneur Blog的博主)有一个还

Java Servlet 编程及应用之三

GenericServlet 类可以说时JSDK 中最重要的类,也是最基本的类.程序员要编写Servlet 应用程序,一般要继承JSDK 提供的GenericServlet 类或它的子类HttpServlet 类. 编程思路:下面是一个简单的例子,在服务器端执行它,就是向客户端的浏览器中输出"HELLO WORLD "以及服务器的时间等信息. HelloWorldServlet.java 的源代码如下: import java.io.*;import java.util.Date;im

Tomcat与Java Web开发技术详解连载之三

web|详解 2.2.8 创建并发布WAR文件 Tomcat既可以运行采用开放式目录结构的Web应用,也可以运行WAR文件.在本书配套光盘的sourcecode/chapter2/helloapp目录下提供了所有源文件,只要把整个helloapp目录拷贝到/webapps目录下,即可运行开放式目录结构的helloapp应用.在Web应用的开发阶段,为了便于调试,通常采用开放式的目录结构来发布Web应用,这样可以方便地更新或替换文件.如果开发完毕,进入产品发布阶段,应该将整个Web应用打包为WAR

开源以后 Java真的能继续创造神话吗?

在柏林的JBoss世界论坛上,JBoss的成立者兼领导者Marc Fleury说到:在Linux世界,他很高兴Sun公司选择在GPL下发布Java,因为他认为:"这将至少延长Java 15年寿命".照自由软件运动的观点来说:Java变成GPL的,这是一个好消息,因为它对Java虚拟机的知识产权产生了保护作用. 一.开源将大大延长Java的寿命 Fleury相信Java虚拟机在开源组织的影响下将会更快地发展,sun公司也会因为保留商标权而受益,而这对于JBoss.开发者以及开源社区来说,