如何在Java中初始化ThreadLocal对象

32

我遇到了一个问题,我正在创建一个ThreadLocal并用new ThreadLocal进行初始化。问题在于,我实际上只是想要一个持久的列表,它可以在线程的生命周期内一直存在,但我不知道有没有一种方法在Java中每个线程中初始化这样的东西。

例如,我想要的是像这样的东西:

ThreadLocal static {
  myThreadLocalVariable.set(new ArrayList<Whatever>());
}

所以它为每个线程初始化。我知道我可以这样做:

private static Whatever getMyVariable() {
  Whatever w = myThreadLocalVariable.get();
  if(w == null) {
    w = new ArrayList<Whatever>();
    myThreadLocalVariable.set(w);
  }
  return w; 
}

但我真的不想每次使用时都进行检查。这里有更好的方法吗?

4个回答

47

你只需覆盖initialValue()方法:

private static ThreadLocal<List<String>> myThreadLocal =
    new ThreadLocal<List<String>>() {
        @Override public List<String> initialValue() {
            return new ArrayList<String>();
        }
    };

2
我知道这个问答现在有点老了,但截至目前为止,官方的JDK8文档在类描述中有一个例子,正是使用initialValue()方法。参考:https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html - kevinarpe
10
现在还有一个适用于Lambda表达式的静态工厂方法,可以帮助你,例如:ThreadLocal.withInitial(ArrayList::new) - kevinarpe
@JonSkeet 我不确定是否应该在我的一个SO问题这里中使用ThreadLocal。想知道你是否能帮忙。我还没有得到任何回应,我很困惑是否使用ThreadLocal是正确的选择。 - user1950349

25

在JDK8中,原先被接受的答案已经过时。以下是此后最佳的方法:

private static final ThreadLocal<List<Foo>> A_LIST = 
    ThreadLocal.withInitial(ArrayList::new);

1
谢谢。该字段可能不应该大写,因为它不是一个常量(列表是可变的)。如果它是常量,那么就没有必要将其设置为线程本地的。您还在使用原始类型,这会引入编译器警告。应该是 ThreadLocal<List<Foo>>。 :) - Michael
该字段是一个常量引用,不管所引用的对象是否可变。 - Oliv
1
惯例是将常量大写,而不是常量引用。请参阅《Effective Java》的第4章,这是关于Java应该参考的权威: “按照惯例,这样的字段的名称由大写字母组成,单词之间用下划线分隔。这些字段包含的内容至关重要可能是基本值或者是对不可变对象的引用。包含对可变对象引用的最终(final)字段具有与非final字段所有相同的缺点。虽然无法修改引用,但是引用的对象可以被修改——结果可能是灾难性的。” - Michael
这里讨论的是常量的使用,而不是命名风格。每当一个字段是“static final”时,它就是一个常量。无论如何,这只是一种惯例。我使用这个惯例,据我记得,大多数人也是这样做的。 - Oliv
每当一个字段被声明为static final时,它就是一个常量。哈哈哈哈 - Michael
显示剩余4条评论

20

你的解决方案很好。可以稍作简化:

private static Whatever getMyVariable() 
{
    Whatever w = myThreadLocalVariable.get();
    if(w == null) 
        myThreadLocalVariable.set(w=new Whatever());
    return w; 
} 

Java 8中,我们能够进行以下操作:
ThreadLocal<ArrayList<Whatever>> myThreadLocal = ThreadLocal.withInitial(ArrayList::new);

使用 Supplier<T> 函数式接口。

1
Java 8的一个有趣评论是它看起来能够更简洁地进行初始化。 - B T
1
最初它被设计为 new ThreadLocal<>(lazyInitializer)。Doug Lea 反对这会给每个 ThreadLocal 实例添加一个新字段。设计被更改为使用工厂方法,该方法实例化 ThreadLocal 的子类。现有程序使用 new ThreadLocal() 仍将获得一个好的旧 ThreadLocal 实例,没有任何变化。 - ZhongYu
如何在本地线程实现中指定数组列表的 capacitysize,例如 ArrayList<>(size)? - Santosh Ravi Teja

0

线程本地存储是每个线程拥有其自己独立变量的区域,以便实现并发和并行。

以下是初始化SimpleDateFormat对象的ThreadLocalVariable的示例。

public class ThreadLocalTest {


private static ExecutorService threadPool = Executors.newFixedThreadPool(10);


//Thread Local SimpleDateFormat
public static ThreadLocal<SimpleDateFormat> dateFormatter = new ThreadLocal<>() {
    @Override
    protected SimpleDateFormat initialValue() {
        return new SimpleDateFormat("yyyy-MM-dd");
    }

    @Override
    public SimpleDateFormat get() {
        return super.get();
    }
};


public static void main(String[] args) throws InterruptedException {

    for (int i = 0; i < 100; i++) {

        threadPool.submit(()-> {
            String birthDate = new ThreadLocalV3().birthdate(100);
            System.out.println(birthDate);
        });

    }

    Thread.sleep(1000);
}


public String birthdate(int userId) {

    Date birthDate = new Date(); //instead of new Date()  suppose we need to fetch birthdate bu userId

    //now each thread will have it's own sdf object
    final SimpleDateFormat sdf = ThreadLocalTest.dateFormatter.get();

    return sdf.format(birthDate);
}
}

在Java 8版本之后,您可以使用Lambda表达式来实现ThreadLocal初始化,如下所示:

public static ThreadLocal<SimpleDateFormat> dateFormatter = ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd"));

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接