1

I have a class as

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.experimental.FieldDefaults;
import lombok.extern.slf4j.Slf4j;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;



/***
 * Builds the required employee jod data records by fetching the same from
 * the available sources(PDL for now) and validating the response.
 */
@Slf4j
@AllArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
public class EmployeeJobDataBuilder {

    EmployeeJobDataAccessor employeeJobDataAccessor;
    private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");


   
    private List<EmployeeJobData> filterEmployeeJobDataEntriesForStringDates(final List<EmployeeJobData> preFilterEntries,
                                                                             final String rangeStartDateString,
                                                                             final String rangeEndDateString,
                                                                             final EmployeeJobData filterJobData){
        if(CollectionUtils.isNullOrEmpty(preFilterEntries)){
            log.info("filterJobDataEntriesForStringDates: preFilterEntries.size- 0");
            return Collections.emptyList();
        }
        log.info("filterJobDataEntriesForStringDates: preFilterEntries.size- {}", preFilterEntries.size());

        List<EmployeeJobData> filteredEntries = preFilterEntries.stream()
                .filter(entry -> this.parseAndCompare(entry.getEffEndDateString(), rangeStartDateString))
                .filter(entry -> this.parseAndCompare(rangeEndDateString, entry.getEffStartDateString()))
                .collect(Collectors.toList());

        log.info("filterJobDataEntriesForStringDates: filteredEntries.size: {}", filteredEntries.size());

        return filteredEntries;
    }

    private boolean parseAndCompare(final String date1, final String date2){
        try {
            return this.dateFormat.parse(date1).compareTo(this.dateFormat.parse(date2)) >= 0;
        } catch (final ParseException parseException) {
            log.warn("Exception occurred while parsing string date");
        }
        return false;
    }

   
}

Here I am using SimpleDateFormat as defined above and parsing using it, however I get inconsistent results, the call which fails for some parameters gets succeeded the next time within milliseconds with same parameters (What I mean is that the parsing gives incorrect results which I why it happens)

I referred some articles Why is Java's SimpleDateFormat not thread-safe? saying that this Java Library is not thread safe and that is why it happens

Also the class in which this date format is being used is a Singleton class with definition as:-

@Provides
    @Singleton
    public EmployeeJobDataBuilder getEmployeeJobDataBuilder(final EmployeeJobDataAccessor employeeJobDataAccessor){
        return new EmployeeJobDataBuilder(employeeJobDataAccessor);
    }

Is it related to Thread safety issue?

Because I tried to mock it with a test class having this behaviour

import edu.emory.mathcs.backport.java.util.concurrent.ConcurrentHashMap;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.util.Date;
import java.util.Map;

class MultithreadingDemo extends Thread {

    final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    Map<Date, Integer> map = new ConcurrentHashMap();
    public void run()
    {
        try {


            final String payEndDateString = LocalDate.parse("2021-12-18").atTime(LocalTime.MAX)
                    .atZone(ZoneId.of("America/New_York")).toString();

            Date d = dateFormat.parse(payEndDateString);
            map.put(d,1);

            if(map.size()>=2) {
                System.out.println("Yes" + map);
            }

            } catch (ParseException parseException) {
            System.out.println(parseException);
        }
        catch (Exception e) {
            // Throwing an exception
            System.out.println("Exception is caught");
        }
    }
}
 
// Main Class
public class Multithread {
    public static void main(String[] args) {
        int n = 800000; // Number of threads
        for (int i = 0; i < n; i++) {
            MultithreadingDemo object
                    = new MultithreadingDemo();
            object.start();
        }
    }
}

Here in my opinion I should have got 2 values in map if parsing gives incorrect results at any point in time but I was unable to see this.

Can someone please help?

Saksham Agarwal
  • 135
  • 2
  • 9
  • 2
    `SimpleDateFormat` is not thread safe and can’t be accessed from multiple threads. It must be protected by a lock. That same is not true of `DateTimeFormatter`. Your test is not correct as each thread has its own instance. – Boris the Spider Dec 17 '21 at 07:00
  • Okay I get this point w.r.t my example given. Can you help me modify this example so that I can simulate the actual behaviour mentioned above in code? I am new to threads so finding a bit difficult. – Saksham Agarwal Dec 17 '21 at 07:10
  • Just stick `static` in front of the formatter declaration. Dirty hack, but should do the trick. – Boris the Spider Dec 17 '21 at 07:14
  • Yeah I did use that but in my actual example I dont define it as static so wanted to be close to that as possible – Saksham Agarwal Dec 17 '21 at 07:17
  • You have a DI framework, so if you want it as close as possible create a small DI application with your formatter as a singleton. Fundamentally what you need is all your threads to share a formatter; however you achieve that. – Boris the Spider Dec 17 '21 at 07:18
  • Okay cool. And the fix for this would be to use DateTimeFormatter as you mentioned the same way I have used SimpleDateFormat as it is thread safe, is that correct? – Saksham Agarwal Dec 17 '21 at 07:23

0 Answers0