驯服Java线程(三)

原创 2003年03月10日 10:17:00

接上回!

避免同步

大部分显示的同步都可以避免。一般不操作对象状态信息(例如数据成员)的方法都不需要同步,例如:一些方法只访问本地变量(也就是说在方法内部声明的变量),而不操作类级别的数据成员,并且这些方法不会通过传入的引用参数来修改外部的对象。符合这些条件的方法都不需要使用synchronization这种重量级的操作。除此之外,还可以使用一些设计模式(Design Pattern)来避免同步(我将会在后面提到)。

你甚至可以通过适当的组织你的代码来避免同步。相对于同步的一个重要的概念就是原子性。一个原子性的操作事不能被其他线程中断的,通常的原子性操作是不需要同步的。

Java定义一些原子性的操作。一般的给变量付值的操作是原子的,除了longdouble。看下面的代码:

class Unreliable

{

private long x;

 <?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

public long get_x( ) {return x;}

 

public void set_x(long value) { x = value; }

}

线程1调用:

obj.set_x( 0 );

线程2调用:

obj.set_x( 0x123456789abcdef )

问题在于下面这行代码:

x = value;

JVM为了效率的问题,并没有把x当作一个64位的长整型数来使用,而是把它分为两个32-bit,分别付值:

x.high_word = value.high_word;

x.low_word = value.low_word;

    因此,存在一个线程设置了高位之后被另一个线程切换出去,而改变了其高位或低位的值。所以,x的值最终可能为0x0123456789abcdef0x012345670000000x00000000abcdef0x00000000000000。你根本无法确定它的值,唯一的解决方法是,为set_x( )get_x()方法加上synchronized这个关键字或者把这个付值操作封装在一个确保原子性的代码段里。

所以,在操作的long型数据的时候,千万不要想当然。强迫自己记住吧:只有直接付值操作是原子的(除了上面的例子)。其它,任何表达式,象x = ++yx += y都是不安全的,不管xy的数据类型是否是小于64位的。很可能在付值之前,自增之后,被其它线程抢先了(preempted)。

竞争条件

       在术语中,对于前面我提到的多线程问题——两个线程同步操作同一个对象,使这个对象的最终状态不明——叫做竞争条件。竞争条件可以在任何应该由程序员保证原子操作的,而又忘记使用synchronized的地方。在这个意义上,可以把synchronized看作一种保证复杂的、顺序一定的操作具有原子性的工具,例如给一个boolean值变量付值,就是一个隐式的同步操作。

不变性

一种有效的语言级的避免同步的方法就是不变性(immutability)。一个自从产生那一刻起就无法再改变的对象就是不变性对象,例如一个String对象。但是要注意类似这样的表达式:string1 += string2;本质上等同于string1 = string1 + string2;其实第三个包含string1string2string对象被隐式的产生,最后,把string1的引用指向第三个string。这样的操作,并不是原子的。

由于不变对象的值无法发生改变,所以可以为多个线程安全的同步操作,不需要synchronized

把一个类的所有数据成员都声明为final就可以创建一个不变类型了。那些被声明为final的数据成员并不是必须在声明的时候就写死,但必须在类的构造函数中,全部明确的初始化。例如:

Class I_am_immutable

{

private final int MAX_VALUE = 10;

private final int blank_final;

 

public I_am_immutable( int_initial_value )

{

      blank_final = initial_value;

}

}

一个由构造函数进行初始化的final型变量叫做blank final。一般的,如果你频繁的只读访问一个对象,把它声明成一个不变对象是个保证同步的好办法,而且可以提高JVM的效率,因为HotSpot会把它放到堆栈里以供使用。

同步封装器(Synchronization Wrappers

       同步还是不同步,是问题的所在。让我们跳出这样的思维模式吧,世事无绝对。有什么办法可以使你的类灵活的在同步与不同步之间切换呢? 有一个非常好的现成例子,就是新近引入JAVACollection框架,它是用来取代原本散乱的、繁重的Vector等类型。Vector的任何方法都是同步的,这就是为什么说它繁重了。而对于collections对象,在需要保证同步的时候,一般会由访问它方法来保证同步,因此没有必要两次锁定(一次是锁定包含使用collection对象的方法的对象,一次是锁定collection对象自身)。Java的解决方案是使用同步封装器。其基本原理来自四人帮(Gang-of-Four)的Decorator模式,一个Decorator自身就实现某个接口,而且又包含了实现同样接口的数据成员,但是在通过外部类方法调用内部成员的相同方法的时候,控制或者修改传入的变量。java.io这个包里的所有类都是Decorator:一个BufferedInputStream既实现了虚类InputStream的所有方法,又包含了一个InputStream引用所指向的成员变量。程序员调用外部容器类的方法,实际上是变相的调用内部对象的方法。

       我们可以利用这种设计模式。来实现灵活的同步方法。如下例:

Interface Some_interface

{

Object message( );

}

 

class Not_thread_safe implements Some_interface

{

public Object message( )

{

     //实现该方法的代码,省~~~~~~~~~~~

     return null;

}

}

 

class Thread_safe_wrapper implements Some_interface

{

Some_interface  not_thread_safe;

 

public Thread_safe_wrapper(Some_interface  not_thread_safe)

{

     this.not_thread_safe  =  not_thread_safe;

}

 

public Some_interface extract( )

{

      return not_thread_safe;

}

 

public synchronized Object message( )

{

     return not_thread_safe.message( );

}

}

       当不存在线程安全的时候,你可以直接使用Not_thread_safe类对象。当需要考虑线程安全的时候,只需要把它包装一下:

       Some_interface object = new Not_thread_safe( );

       ……………

       object = new Thread_safe_wrapper(object); //object现在变成线程安全了

当你不需要考虑线程安全的时候,你可以还原object对象:

       object = ((Thread_safe_Wrapper)object).extract( );

 

下一回,我们又要深入底层机制了。呵呵!千万不要闷着大家呀!下回见!

驯服Java线程(二)

Java 线程的支持不是平台独立的非常不幸,作为Java语言所保证的平台独立性最重要的组成部分-------Java线程,并非是平台独立的。这增加了实现不依赖于平台的线程系统的难度。在实现的时候,不得...
  • Norwaywoods
  • Norwaywoods
  • 2003年03月08日 15:03
  • 1273

驯服Java线程

线程和进程(Threads and Processes) 第一个关键的系统级概念,究竟什么是线程或者说究竟什么是进程?她们其实就是操作系统内部的一种数据结构。 进程数据结构掌握着所有与内存相关的东西:...
  • daniel112
  • daniel112
  • 2009年04月16日 15:10
  • 793

驯服Java线程(四)

线程的并发性       下一个与OS平台相关的问题(这也是编写与平台无关的Java程序要面对的问题)是必须确定并发性和并行在该平台的定义。并发的多线程系统总会给人多个任务同时运行的感觉,其实这些任务...
  • Norwaywoods
  • Norwaywoods
  • 2003年03月21日 21:23
  • 4721

驯服Java线程(一)

JAVA 线程架构Java 多线程编程其实并不象大多数的书描述的那样简单,所有关于UI(用户界面)的Java编程都要涉及多线程。这一章将会通过讨论几种操作系统的线程架构和这些架构将会怎样影响Java多...
  • Norwaywoods
  • Norwaywoods
  • 2003年03月05日 09:03
  • 1568

阅读《驯服烂代码》

在新做一个项目之前阅读了本书,记录下印象深刻的部分。 作者刚开始描述的做酒店时钟系统的开发流程。由文档到设计到系统,完全按照正常的开发流程。作者采用领域模型订阅和术语表定义的方式明确需求。然后用...
  • shuiyue0626
  • shuiyue0626
  • 2015年12月09日 20:07
  • 501

读《驯服烂代码——在编程操练中悟道》

读《驯服烂代码——在编程操练中悟道》 第2章 按图索骥地编写代码 第4章 调试一下 第5章 用TDD重做编程操练题目 第6章 消除假数据所带来的重复代码 第8章 嗅出代码腐臭和新的测试点 第9章 测试...
  • q547550831
  • q547550831
  • 2016年06月26日 19:59
  • 2924

JAVA三个线程依次打印ABC

一、一些简单概述        多线程情形下对共享资源的访问是需要互斥的,比如对一个变量的读写,一个线程读写这个变量的时候,其它线程就不能对这个变量进行读写。Java提供了synchronized关...
  • GAMEloft9
  • GAMEloft9
  • 2016年07月07日 15:29
  • 1115

多线程编程中三个特性

原子性,可见性,有序性简述
  • xiong_hui_hui
  • xiong_hui_hui
  • 2016年04月25日 12:27
  • 2417

多线程三大特性

多线程编程中三个特性 标签: 多线程 2016-04-25 12:27 1560人阅读 评论(0) 收藏 举报 分类: java(6) 版权声明:本文为博主原创文章,未经...
  • fmm_sunshine
  • fmm_sunshine
  • 2017年09月25日 13:46
  • 257

顺序打印ABC------java多线程的一道经典面试题

这是迅雷的一道面试题,顺序打印ABC十次。 源代码如下 public class PrintABC { public static Boolean isThreadA = true; pub...
  • shinehuaking2011
  • shinehuaking2011
  • 2012年10月25日 18:59
  • 8635
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:驯服Java线程(三)
举报原因:
原因补充:

(最多只允许输入30个字)