ThreadLocalRandom的BUG浅析

Java中有一个用来生随机数的类,java.util.Random。用起来很简单,

java.util.Random rand=new java.util.Random();
int v=rand.nextInt();

众所周知,这样生出来的随机数是伪随机数,于是需要一个种子(seed),所以如果把这个对象在多线程共享,就会产生同步开销。 jdk 1.7引入了一个新类,ThreadLocalRandom。用起来更简单了,啥时候想用就用:

int v=ThreadLocalRandom.current().nextInt();

不过,悲剧来了,我测了一下,发现它每次产生的值都是一样的。这是JDK一个尚未修复的BUG。下面将详细讲述这个BUG如何产生的,希望对所有使用面向对象设计模式的人有所警示。 先从java.util.Random这个类说起,它有两个构造函数:

public Random(long seed) ;
public Random() ;

如果你提供了seed,它就用你的seed,否则,它就随机生一个seed。所以,不带参数的版本,代码是这样的:

public Random() {
this(seedUniquifier() ^ System.nanoTime());
}

带参数的版本,本来是这样实现的: (我推测)

public Random(long seed) {
 setSeed(seed);
}

private final AtomicLong seed=new AtomicLong ();
synchronized public void setSeed(long seed) {
 this.seed.set(initialScramble(seed));
}

然后我们实现一个子类ThreadLocalRandom,继承自Random

public class ThreadLocalRandom extends Random {
    private long rnd; //用普通的long就可以了,不需要AtomicLong。

    ThreadLocalRandom() {
      super();
    }

    public void setSeed(long seed) {
      rnd = seed ;
    }
}

你想,既然这个类的实例是ThreadLocal的,所以它的所有成员变量只有在当前线程能访问到,就可以把所有的同步机制去掉了,于是我们就把种子的类型从AtomicLong换成了long。 ThreadLocalRandom的默认构造函数会调用父类Random的默认构造函数,然后它又会调用setSeed这个方法,setSeed在子类被重新实现了。OK,没问题。 但是某一天,因为一个bug,http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6937857 (可能是这个),父类的实现改了,父类的构造函数不再调用setSeed方法了

public Random(long seed) {
    this.seed = new AtomicLong(initialScramble(seed));
}

于是子类ThreadLocalRandom的setSeed方法永远不会被调用!于是现象就是:种子的初始值一定是0,于是总是产生固定的序列。这个BUG从10年5月被发现,至今状态仍然是“已关闭、未确认!”http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6955840 (感谢itgir提供此链接) 再看一个例子:

class Counter{
    int v;
    void addValue(int d){
        v+=d;
    }

    void decreaseValue(int d){
        this.addValue(-d);
    }
}

某人想加一行日志,当value每次改变的时候,都加一行日志,于是代码就成这样了:

class MyCounter extends Counter{
    void addValue(int d){
        super(d);
        logger.debug("new value="+v);
    }
}

然后某一天,有人改了父类实现:

class Counter{
  int v;
  void addValue(int d){
    v+=d;
  }

  void decreaseValue(int d){
    v-=d;
  }
}

于是子类就悲剧了。 说说你的感受吧,如何在开发中避免这样的BUG?至少JDK的这个BUG是非常严重的!因为系统中很多地方都会用到随机数,尤其是安全模块。如果谁一不小心用了ThreadLocalRandom……

此博客中的热门博文

少写代码,多读别人写的代码

在windows下使用llvm+clang

tensorflow distributed runtime初窥