谓词和日期的线程安全问题

Posted

技术标签:

【中文标题】谓词和日期的线程安全问题【英文标题】:Thread Safety issue with Predicates and Date 【发布时间】:2021-11-14 06:17:30 【问题描述】:

我有一个谓词列表。我使用这个列表来匹配一些实体,但是当我使用带有日期的谓词时,我有时会得到一个ArrayIndexOutOFBoundException。我不确定为什么会这样。任何指针将不胜感激。

这就是代码的样子。异常发生在循环内的谓词检查中。

private final SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
formatter.setTimeZone(TimeZone.getDefault());

final Map<String, Integer> values = new HashMap<>();
final List<Entry<String, Predicate<Entity>>> predicates = new ArrayList<>();

predicates.add(new SimpleEntry<>("date", entity -> entity.getDate() != null && dateStr.equalsIgnoreCase(this.formatter.format(entity.getDate()))));  // Line 90

/* Other predicates */

final Predicate<Entity> predicateChain = predicates.stream().map(Entry::getValue).reduce(predicate -> false, Predicate::or);

entities.
    parallelStream()
    forEach(entity -> 

       if (!predicateChain.test(entity)) return;

       final short value = (short) predicates.stream().mapToInt(predicate -> 
                                if (predicate.getValue().test(entity)) return values.get(predicate.getKey());
                                return 0;
                            ).sum();
    );

这是我得到的例外。

java.lang.ArrayIndexOutOfBoundsException: 299
    at sun.util.calendar.BaseCalendar.getCalendarDateFromFixedDate(BaseCalendar.java:453)
    at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2397)
    at java.util.GregorianCalendar.computeFields(GregorianCalendar.java:2312)
    at java.util.Calendar.complete(Calendar.java:2268)
    at java.util.Calendar.get(Calendar.java:1826)
    at java.util.Calendar.getDisplayName(Calendar.java:2087)
    at java.text.SimpleDateFormat.subFormat(SimpleDateFormat.java:1125)
    at java.text.SimpleDateFormat.format(SimpleDateFormat.java:966)
    at java.text.SimpleDateFormat.format(SimpleDateFormat.java:936)
    at java.text.DateFormat.format(DateFormat.java:345)
    at com.company.service.EntityService.lambda$null$1(EntityService.java:90)
    at java.util.function.Predicate.lambda$or$2(Predicate.java:101)
    at com.company.service.EntityService.lambda$search$22(EntityService.java:211)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
    at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
    at java.util.concurrent.ForkJoinTask.doExec$$$capture(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java)
    at java.util.concurrent.ForkJoinPool.helpComplete(ForkJoinPool.java:1881)
    at java.util.concurrent.ForkJoinPool.externalHelpComplete(ForkJoinPool.java:2478)
    at java.util.concurrent.ForkJoinTask.externalAwaitDone(ForkJoinTask.java:324)
    at java.util.concurrent.ForkJoinTask.doInvoke(ForkJoinTask.java:405)
    at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:734)
    at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:160)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(ForEachOps.java:174)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:583)
    at com.company.service.EntityService.search(EntityService.java:206)
    at com.company.controllers.EntityController(BaseEntityController.java:34)
    at com.company.controllers.EntityController.lambda$Search$0(Search.java:38)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1384)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
    at java.util.stream.ForEachOps$ForEachTask.compute(ForEachOps.java:291)
    at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
    at java.util.concurrent.ForkJoinTask.doExec$$$capture(ForkJoinTask.java:289)
    at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java)
    at java.util.concurrent.ForkJoinTask.doInvoke(ForkJoinTask.java:401)
    at java.util.concurrent.ForkJoinTask.invoke(ForkJoinTask.java:734)
    at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(ForEachOps.java:160)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(ForEachOps.java:174)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:233)
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
    at java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:583)
    at com.company.controllers.EntityContoller(BaseEntityController.java:34)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.ws.server.endpoint.MethodEndpoint.invoke(MethodEndpoint.java:134)
    at org.springframework.ws.server.endpoint.adapter.DefaultMethodEndpointAdapter.invokeInternal(DefaultMethodEndpointAdapter.java:291)
    at org.springframework.ws.server.endpoint.adapter.AbstractMethodEndpointAdapter.invoke(AbstractMethodEndpointAdapter.java:55)
    at org.springframework.ws.server.MessageDispatcher.dispatch(MessageDispatcher.java:236)
    at org.springframework.ws.server.MessageDispatcher.receive(MessageDispatcher.java:176)
    at org.springframework.ws.transport.support.WebServiceMessageReceiverObjectSupport.handleConnection(WebServiceMessageReceiverObjectSupport.java:89)
    at org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter.handle(WebServiceMessageReceiverHandlerAdapter.java:61)
    at org.springframework.ws.transport.http.MessageDispatcherServlet.doService(MessageDispatcherServlet.java:293)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:660)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:741)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:108)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:202)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:526)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343)
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408)
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66)
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:861)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1579)
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)

【问题讨论】:

SimpleDateFormat: “日期格式不同步。建议为每个线程创建单独的格式实例。如果多个线程同时访问一个格式,它必须在外部同步。 如果您使用 java.time 和 java.time.format 中的日期和格式化程序,这不是问题。 【参考方案1】:

正如@Holger 所说,SimpleDateFormat 不是线程安全的。您需要将 SimpleDateFormat 初始化到并行循环中,例如以下几行:

private final SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
formatter.setTimeZone(TimeZone.getDefault());

所以Predicate的初始化就变成了:

predicates.add(new SimpleEntry<>("date", entity -> 

    SimpleDateFormat formatter = new SimpleDateFormat("dd/MM/yyyy");
    formatter.setTimeZone(TimeZone.getDefault());

    return entity.getDate() != null && dateStr.equalsIgnoreCase(formatter.format(entity.getDate()));
));

【讨论】:

以上是关于谓词和日期的线程安全问题的主要内容,如果未能解决你的问题,请参考以下文章

JAVA8新的时间与日期 API- 传统时间格式化的线程安全问题

日期格式化:SimpleDateFormat线程不安全FastDateFormat和Joda-Time后两个都是线程安全

强大易用的日期和时间库 线程安全 Joda Time

C++ - 如何以独立于平台、线程安全的方式以用户首选的日期/时间语言环境格式格式化文件的最后修改日期和时间

Java-JUC(十四):SimpleDateFormat是线程不安全的

(转)关于SimpleDateFormat安全的时间格式化线程安全问题