Java 8 到 Java 17 ThreadLocal 问题

Boc*_*909 6 java multithreading thread-local completable-future java-17

我的代码在 Java 8 中运行良好,但是当我将其迁移到 Java 17 时,它就不起作用了。它涉及 ThreadLocal 和 CompletableFuture.runAsync。

以下是课程:

public class UriParameterHandler {
}

public class DateRangeEntity {
    public String getCurrentDate(){
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        LocalDateTime now = LocalDateTime.now();
        return dtf.format(now);
    }
}

public class SessionHandler {

    private static ThreadLocal<SessionHandler> instance = new InheritableThreadLocal<>();
    private UriParameterHandler uriParameterHandler;
    private DateRangeEntity dateRangeEntity;

    private SessionHandler() {
        instance.set(this);
    }

    public static void initialize() {
        SessionHandler handler = new SessionHandler();
        handler.uriParameterHandler = new UriParameterHandler();
    }

    public static UriParameterHandler getUriParameterHandler() {
        return instance.get().uriParameterHandler;
    }

    public static void setUriParameterHandler(UriParameterHandler uriParameterHandler) {
        instance.get().uriParameterHandler = uriParameterHandler;
    }

    public static DateRangeEntity getDateRangeEntity() {
        return instance.get().dateRangeEntity;
    }

    public static void setDateRangeEntity(DateRangeEntity dateRangeEntity) {
        instance.get().dateRangeEntity = dateRangeEntity;
    }
}

public class LocalThread implements Runnable{
    @Override
    public void run() {
        if(SessionHandler.getDateRangeEntity()!=null){
            System.out.println("not null");
        }else{
            System.out.println("is null");
        }
    }
}

public class SessionHandlerMain {

    public static void main(String[] args) {
        threadLocalDemo();
    }

    private static void threadLocalDemo(){
        SessionHandler.initialize();
        SessionHandler.setDateRangeEntity(new DateRangeEntity());

        //works in java8 but not in java17
        CompletableFuture.runAsync(()->{
            if(SessionHandler.getDateRangeEntity()!=null){
                System.out.println("not null");
            }else{
                System.out.println("is null");
            }

        }).exceptionally(e->{
            e.printStackTrace();
            return null;
        });

        /*
        LocalThread localThread = new LocalThread();
        localThread.run();
         */
    }
}
Run Code Online (Sandbox Code Playgroud)

threadLocalDemo()in 中SessionHandlerMain,我首先DateRangeEntity为 the 设置新对象SessionHandler,然后在runAsync()方法中调用 thegetDateRangeEntity()来检查该对象是否不为空。这在 Java 8 中有效并打印“not null”,但是当我迁移到 Java 17 时,现在会抛出此异常:

public class UriParameterHandler {
}

public class DateRangeEntity {
    public String getCurrentDate(){
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        LocalDateTime now = LocalDateTime.now();
        return dtf.format(now);
    }
}

public class SessionHandler {

    private static ThreadLocal<SessionHandler> instance = new InheritableThreadLocal<>();
    private UriParameterHandler uriParameterHandler;
    private DateRangeEntity dateRangeEntity;

    private SessionHandler() {
        instance.set(this);
    }

    public static void initialize() {
        SessionHandler handler = new SessionHandler();
        handler.uriParameterHandler = new UriParameterHandler();
    }

    public static UriParameterHandler getUriParameterHandler() {
        return instance.get().uriParameterHandler;
    }

    public static void setUriParameterHandler(UriParameterHandler uriParameterHandler) {
        instance.get().uriParameterHandler = uriParameterHandler;
    }

    public static DateRangeEntity getDateRangeEntity() {
        return instance.get().dateRangeEntity;
    }

    public static void setDateRangeEntity(DateRangeEntity dateRangeEntity) {
        instance.get().dateRangeEntity = dateRangeEntity;
    }
}

public class LocalThread implements Runnable{
    @Override
    public void run() {
        if(SessionHandler.getDateRangeEntity()!=null){
            System.out.println("not null");
        }else{
            System.out.println("is null");
        }
    }
}

public class SessionHandlerMain {

    public static void main(String[] args) {
        threadLocalDemo();
    }

    private static void threadLocalDemo(){
        SessionHandler.initialize();
        SessionHandler.setDateRangeEntity(new DateRangeEntity());

        //works in java8 but not in java17
        CompletableFuture.runAsync(()->{
            if(SessionHandler.getDateRangeEntity()!=null){
                System.out.println("not null");
            }else{
                System.out.println("is null");
            }

        }).exceptionally(e->{
            e.printStackTrace();
            return null;
        });

        /*
        LocalThread localThread = new LocalThread();
        localThread.run();
         */
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,如果我将方法的逻辑移动runAsync()到扩展的类中,Runnable那么这在 Java 17 中是有效的。

有人可以告诉我为什么这种行为在 Java 17 中不同,以及是否有其他解决方法?

Seb*_*edl 11

您的代码已损坏,并且它在大多数情况下都可以工作只是偶然。

AThreadLocal提供每个线程唯一的存储位置。不同的线程,不同的存储位置。这意味着如果您在不同的线程上,您应该期望得到不同的东西ThreadLocal

您运行代码,使用单个参数查询ThreadLocala 内部。CompletableFuture.runAsync这意味着代码将在ForkJoinPool.commonPool()执行器上运行,即在线程池中的某个线程上运行。

您绝对不应该依赖ThreadLocal此类代码中 a 内的任何值。


那么为什么代码大部分时间都能工作呢?这是因为你创建了ThreadLocal一个InheritableThreadLocal(这对于“存储服务实例”来说是一个坏主意 - 要么你的服务是线程安全的,你应该全局使用一个,要么它不是(就像你的一样)并在线程之间共享它被打破)。这个类的特殊之处在于,当创建一个新线程时, anInheritableThreadLocal被初始化为与父线程(即创建新线程的线程)相同的值,而普通的ThreadLocal只是初始化为null

未指定何时以及如何ForkJoinPool.commonPool()初始化。最可能的策略是延迟创建,即在第一次调用方法时创建池。因此,通常,池是在您调用 时创建的CompletableFuture.runAsync(),因此池中的线程是主线程的子线程,继承您已存储在ThreadLocal.

然而,在 Java 17 中,似乎有些东西会在您初始化 之前初始化池ThreadLocal,因此它不会继承您设置的值。

然而,即使其他 Java 版本可能没有相同的行为,我仍然认为组合ForkJoinPoolandThreadLocal从根本上来说是错误的。用于ThreadLocal真正线程本地的事情并且本来就是如此。


至于LocalThread类 - 您确实意识到这只是一个run()具有在正常执行中在主线程上调用的方法的类?如果你想在另一个线程上运行它,你需要这样做new Thread(new LocalThread()).start()。但由于您显式启动了该线程,因此可以保证继承ThreadLocal此处的值,因此代码将可靠地工作。(尽管我对缓存服务的线程安全问题仍然存在。)