多线程中的静态方法

时间:2018-04-24 08:45:05

标签: java date simpledateformat

我的课程中有以下代码

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)
    );
}

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

编辑:

我有以下代码来重现和证明答案中提到的内容,但仍然无法解决。可以帮助我做同样的事情。

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());
            }
    }

4 个答案:

答案 0 :(得分:5)

  

是静态方法还是变量负责呢?

静态变量。 SimpleDateFormat不是线程安全的,因为您通过调用setTimeZone()来修改其内部状态,所以这应该是显而易见的。这意味着有几个线程可以同时执行此操作,这应该会产生不可预测的结果。

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

答案 1 :(得分:1)

TL;博士

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

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

当前时刻

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

在您的代码中没有任何地方我看到您抓住当前时刻。如果您想要当前时刻,请拨打Instant.now(),如下所示。

避免遗留日期时间类

旧版日期时间类,例如Date&amp; SimpleDateFormat 是线程安全的。避免这些麻烦的课程的众多原因之一。几年前,它们被 java.time 类取代。

java.time

作为UTC中的一个时刻,java.util.Date类被Instant类替换。同样的想法,但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 类中默认使用的ISO 8601标准。如果中间的T困扰您或您的用户,请使用空格替换。

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

2018-04-24T11:04:30.569Z

ExecutorService阻止

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

添加对ExecutorService::awaitTermination的来电,如下面的代码所示。设置超时时间足够长,如果超出则必须意味着发生了一些问题。引用文档:

  

阻止所有任务在关闭请求完成后执行,或发生超时,或者当前线程中断,以先发生者为准。

请参阅下面的示例代码。有关更多讨论,请参阅此问题ExecutorService - How to wait for completition of all tasks in non-blocking style

线程

java.time 类是thread-safe设计的。他们使用immutable objects模式,根据现有值返回新对象,而不是更改(“变异”)原始对象。

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

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

// String moment = Instant.ofEpochMilli(1524567870569L).toString()。replace(&#34; T&#34;,&#34;&#34;);             String moment = Instant.now()。toString()。replace(&#34; T&#34;,&#34;&#34;);             字符串输出=(时刻+&#34; |&#34; + threadId);             返回输出;         }     };

// 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

答案 2 :(得分:1)

作为编辑的答案:如何用线程不安全再现问题(不确定这是否真的应该是一个单独的问题)。使用相同的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);

答案 3 :(得分:0)

tz实际上是常量,并且在第一次调用任一方法后,setter都不会执行任何操作。使用静态初始化程序立即设置时区以使方法成为线程安全的。

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));
}
相关问题