多线程中的静态方法

Static method in multithreading

我的 class

中有以下代码
private static final SimpleDateFormat SDF_ISO_DATE = new SimpleDateFormat("yyyy-MM-dd");
private static final SimpleDateFormat SDF_ISO_TIME = new SimpleDateFormat("HH:mm:ss");

public static String getTimeStampAsString(final long time) {
    TimeZone tz = TimeZone.getTimeZone("UTC");
    SDF_ISO_DATE.setTimeZone(tz);
    SDF_ISO_TIME.setTimeZone(tz);
    return SDF_ISO_DATE.format(
        new Date(time)) + " " + SDF_ISO_TIME.format(new Date(time)
    );
}

在我的多线程应用程序中,以下方法 returns 将来的日期,即使是当前日期,静态方法或变量是否对此负责?

编辑:

我有以下代码来重现和证明答案中提到的内容,但仍然无法to.Can有人帮助我。

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

            Callable<String> task = new Callable<String>(){
                public String call() throws Exception {
                    return DateUtil.getTimeStampAsString(1524567870569L);
                }
            };

            //pool with 50 threads
            ExecutorService exec = Executors.newFixedThreadPool(50);
            List<Future<String>> results = new ArrayList<Future<String>>();

            //perform 10 date conversions
            for(int i = 0 ; i < 50 ; i++){
                results.add(exec.submit(task));
            }
            exec.shutdown();
            //look at the results
            for(Future<String> result : results){
                System.out.println(result.get());
            }
    }

is the static method or variable is responsible for this?

静态变量。 SimpleDateFormat 不是线程安全的,这一点很明显,因为您正在通过调用 setTimeZone() 修改其内部状态。这意味着多个线程可以同时执行此操作,这应该会产生不可预测的结果。

您需要在本地构建您的格式,而不是重复使用一些静态定义的格式。或者更好的是,放弃 Java 的旧时间管理 类 并使用 java.time.* 代替。

tz 实际上是常量,设置器在第一次调用任一方法后不做任何事情。使用静态初始化程序立即设置时区以使方法线程安全。

private static final SimpleDateFormat SDF_ISO_DATE = new SimpleDateFormat("yyyy-MM-dd");
private static final SimpleDateFormat SDF_ISO_TIME = new SimpleDateFormat("HH:mm:ss");

static {
    TimeZone tz = TimeZone.getTimeZone("UTC");
    SDF_ISO_DATE.setTimeZone(tz);
    SDF_ISO_TIME.setTimeZone(tz);
}

public static String getCurrentTimeStamp(final Date date) {
    return SDF_ISO_DATE.format(date) + " " + SDF_ISO_TIME.format(date);
}

public static String getTimeStampAsString(final long time) {
    return getCurrentTimeStamp(new Date(time));
}

tl;博士

要捕获当前时刻并生成所需格式的字符串(这是标准 ISO 8601 格式的修改形式),请使用 java.time classes。这些 classes 更简单,设计也更好。它们也是线程安全的。

Instant.now().toString().replace( "T" , " " )

当前时刻

您的方法名为 getCurrentTimeStamp(final Date date),但您传递的是现有 Date 对象集,而不是捕捉当前时刻。

在你的代码中我没有看到你捕获当前时刻。如果您想要当前时刻,请调用 Instant.now(),如下所示。

避免遗留日期时间 classes

遗留的日期时间 class 诸如 DateSimpleDateFormat 不是 线程安全的。避免这些麻烦的 classes 的众多原因之一。它们在多年前被 java.time classes.

取代

java.time

作为 UTC 时刻,java.util.Date class 被 Instant class 取代。同样的想法,但是 Instant 的分辨率是纳秒而不是毫秒。 Instant::toString 不像 Date::toString 那样动态注入时区。

要以 UTC 格式捕获当前时刻,请调用静态 Instant.now() 方法。

Instant instant = Instant.now() ;  // Capture current moment in UTC.

将您输入的数字解析为自 UTC 中 1970 年第一时刻的纪元参考以来的毫秒数。

Instant instant = Instant.ofEpochMilli( 1_524_567_870_569L ) ;

instant.toString(): 2018-04-24T11:04:30.569Z

不需要你的代码。不需要您的 DateUtil,如上面的代码所示。不需要自定义格式模式,因为您想要的格式恰好符​​合 java.time classes 中默认使用的 ISO 8601 标准。如果中间的 T 打扰到您或您的用户,请替换为 SPACE。

String output = instant.toString().replace( "T" , " " ) ;

2018-04-24T11:04:30.569Z

ExecutorService 阻塞

你好像误会了ExecutorService::shutdown。该方法 not 阻塞等待任务完成。在您编写代码时,某些任务可能 运行 尚未完成,直到 在您报告结果(部分完成的结果)后

添加对 ExecutorService::awaitTermination 的调用,如下面的代码所示。设置一个足够长的超时时间,如果超过它一定意味着发生了一些问题。引用文档:

Block until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first.

请参阅下面的示例代码。有关更多讨论,请参阅此问题,

线程

java.timeclasses 是 thread-safe by design. They use the immutable objects 模式,returning 新对象基于现有值而不是更改(“变异”)原件。

示例代码。您的问题对您是想要硬编码时刻还是当前时刻感到困惑。通过在此示例中启用注释掉的行来切换到任一个。

Callable < String > task = new Callable < String >() {
    public String call () throws Exception {
        long threadId = Thread.currentThread().getId();

// 字符串矩 = Instant.ofEpochMilli( 1524567870569L ).toString().replace( "T" , " " ); String moment = Instant.now().toString().replace( "T" , " " ); 字符串输出 = ( moment + " | " + threadId ); return输出; } };

// Pool with 5 threads
ExecutorService exec = Executors.newFixedThreadPool( 5 );
List < Future < String > > results = new ArrayList < Future < String > >();

// Perform a certain number of tasks.
int countAssignedTasks = 500;
for ( int i = 0 ; i < countAssignedTasks ; i++ ) {
    results.add( exec.submit( task ) );
}

// Wait for tasks to complete.
Boolean completedBeforeTimeOut = null;
try {
    exec.shutdown();
    completedBeforeTimeOut = exec.awaitTermination( 5 , TimeUnit.SECONDS );  // Block until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current thread is interrupted, whichever happens first.
} catch ( InterruptedException e ) {
    e.printStackTrace();
}

// Report results.
System.out.println( "completedBeforeTimeOut: " + completedBeforeTimeOut );
for ( Future < String > result : results ) {
    try {
        System.out.println( result.get() );
    } catch ( InterruptedException e ) {
        e.printStackTrace();
    } catch ( ExecutionException e ) {
        e.printStackTrace();
    }
}

System.out.println( "BASIL - done." );

当运行.

请注意时间是不是时间顺序。在多线程代码中,您无法预测哪些任务将在何时执行。

2018-04-24 20:24:06.991225Z | 13

2018-04-24 20:24:06.991246Z | 14

2018-04-24 20:24:06.991236Z | 15

2018-04-24 20:24:06.991232Z | 16

2018-04-24 20:24:06.991222Z | 17

2018-04-24 20:24:07.067002Z | 16

2018-04-24 20:24:07.067009Z | 17

作为对您编辑的回答:如何重现线程不安全问题(不确定这是否真的应该是一个单独的问题)。在两个或多个线程中使用相同的 SimpleDateFormat 格式化 相同的 日期似乎进展顺利(至少大多数情况下,不能保证它总是会)。尝试格式化不同的日期时间,很容易得到错误的结果。我这样更改了你的任务:

    AtomicLong time = new AtomicLong(1_524_567_870_569L);

    Callable<String> task = new Callable<String>(){
        @Override
        public String call() {
            return DateUtil.getTimeStampAsString(time.getAndAdd(2_768_461_000L));
        }
    };

当我在输出中也对它们进行排序时,最容易看出结果是错误的,所以我这样做了。我只引用一个 运行 的前几个结果,因为这足以说明问题:

2018-04-24 11:04:30
2018-05-26 12:05:31
2018-06-11 13:06:32
2018-07-29 14:07:33
2018-08-08 15:08:34
2018-10-01 16:09:35
…

预期结果是(通过声明 getTimeStampAsString() 同步获得;之后也排序):

2018-04-24 11:04:30
2018-05-26 12:05:31
2018-06-27 13:06:32
2018-07-29 14:07:33
2018-08-30 15:08:34
2018-10-01 16:09:35
…

第五个打印结果已经将日期全错了,08 而不是 30,而且完整列表中还有更多错误。你可以自己试试。正如您可能知道的那样,确切的结果是不可重现的,但您应该会得到以某种方式出错的结果。

PS这是我的代码,用于按排序顺序打印结果,以防您想尝试:

    //look at the results
    SortedSet<String> sorted = new TreeSet<>();
    for (Future<String> result : results){
        sorted.add(result.get());
    }
    sorted.forEach(System.out::println);