CSDN博客

img Norwaywoods

驯服Java线程(三)

发表于2003/3/10 10:17:00  1152人阅读

接上回!

避免同步

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

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

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

class Unreliable

{

private long x;

 

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( );

 

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

0 0

相关博文

我的热门文章

img
取 消
img