线程安全性的文档化

来源:互联网 发布:未妨惆怅是清狂 知乎 编辑:程序博客网 时间:2024/04/30 09:35

首先说一个错误的观点是“只要是加了synchronized关键字的方法或者代码块就一定是线程安全的,而没有加这个关键字的代码就不是线程安全的”。这种观点认为“线程安全要么全有要么全无”,事实上这是错误的。因为线程安全包含了几种级别:

  1. 不可变的(Immutable):类的实例不可变(不可变类),一定线程安全,如String、Long、BigInteger等。
  2. 无条件的线程安全(Unconditionally ThreadSafe):该类的实例是可变的,但是这个类有足够的的内部同步。所以,它的实例可以被并发使用,无需任何外部同步,如Random和ConcurrentHashMap。
  3. 有条件的线程安全(Conditionally ThreadSafe):某些方法需要为了安全的并发而在外部进行同步,其余方法与无条件的线程安全一致。如Collection.synchronized返回的集合,对它们进行迭代时就需要外部同步。如下代码,当对synchronizeColletcion返回的 collection进行迭代时,用户必须手工在返回的 collection 上进行同步,不遵从此建议将导致无法确定的行为。
      Collection c = Collections.synchronizedCollection(myCollection);     ...  synchronized(c) {      Iterator i = c.iterator(); // Must be in the synchronized block      while (i.hasNext())         foo(i.next());  }
  4. 非线程安全(UnThreadSafe):该类是实例可变的,如需安全地并发使用,必须外部手动同步。如HashMap和HashSet。
  5. 线程对立的(thread-hostile):即便所有的方法都被外部同步保卫,这个类仍不能安全的被多个线程并发使用。这种情况的类很少,不常用。

以上是常用的5种线程安全性的级别,这些级别应该认真编写在类的线程安全注解中,以让用户清楚的知道某个类的线程安全性。synchronized关键字与这个文档毫无关系。

Conditionally-ThreadSafe类必须在文档中指明“哪个方法调用序列需要外部同步,以及在执行这些序列的时候要获得哪把锁。”

如果正在编写的是无条件的线程安全类,就应该考虑使用私有的锁对象来代替同步方法,这样可以防止客户端程序和子类的不同步干扰。下面的代码体现了使用同步方法会造成的子类在无意之中妨碍基类的操作。

import java.util.concurrent.CountDownLatch;import java.util.concurrent.CyclicBarrier;import java.util.concurrent.TimeUnit;/** * 这个程序的运行结果是concurrent1和concurrent2随机出现,syn1都在syn2之后或者之前。 * 由此可说明使用私有对象锁可以避免父子类方法的互相同步的干扰问题。 * 因此,在Effective Java中说私有对象锁尤其适用于那些专门为继承而设计的类中。 */public class Test {    public static void main(String[] args) throws InterruptedException {        final CountDownLatch start = new CountDownLatch(1);        final CyclicBarrier firstSectionOver = new CyclicBarrier(2);        final Son son = new Son();        new Thread(new Runnable() {            public void run() {                try {                    start.await();                    son.syn1();                    firstSectionOver.await();                    son.concurrent1();                } catch (Exception e) {                    e.printStackTrace();                }            }        }).start();        new Thread(new Runnable() {            public void run() {                try {                    start.await();                    son.syn2();                    firstSectionOver.await();                    son.concurrent2();                } catch (Exception e) {                    e.printStackTrace();                }            }        }).start();        start.countDown();    }}class Parent {    private final Object parentLock = new Object();    public synchronized void syn1() throws InterruptedException {        for (int i = 0; i < 10; i++) {            TimeUnit.SECONDS.sleep(1);            System.out.println("syn1");        }    }    public void concurrent1() throws InterruptedException {        synchronized (parentLock) {            for (int i = 0; i < 10; i++) {                TimeUnit.SECONDS.sleep(1);                System.out.println("concurrent1");            }        }    }}class Son extends Parent {    private final Object sonLock = new Object();    public synchronized void syn2() throws InterruptedException {        for (int i = 0; i < 10; i++) {            TimeUnit.SECONDS.sleep(1);            System.out.println("syn2,has no concern about the Method syn1");        }    }    public void concurrent2() throws InterruptedException {        synchronized (sonLock) {            for (int i = 0; i < 10; i++) {                TimeUnit.SECONDS.sleep(1);                System.out.println("concurrent2,,has no concern about the Method concurrent1");            }        }    }}


在上面的代码中,syn2和concurrent2都想成为一个不干扰父类方法并且也不被父类方法干扰的同步方法。从运行的结果来看,concurrent2做到了,但syn2没做到。syn2和syn1由于使用synchronized(this)实现同步而对彼此造成干扰。



0 0
原创粉丝点击