Best way to call common methods from a Callable class?

87 Views Asked by At

I use java classes of type Callable (up to 200 in parallel), which call a method of a tool class (centralizing common methods), and I notice that if this method is not 'synchronized', I get errors. I've tried moving this method to the Callable class, but the problem is still there.

With 'synchronized', it works, but I'm afraid that this setting will slow down performance, as tasks launched in parallel can't then be completely independent and will slow each other down, not running at their maximum speed.

What would be the best way to implement this?

Below, (simplified) extract from my code. Without 'synchronized', there seems to be no error as long as the data comparison doesn't deal with dates. But if the method compares dates (actually, more precisely, a mixture of java.util.Date and Timestamp, which I convert to date, then string), then I get errors. But the method works fine, because as soon as I switch to synchronized, no error...

public class CompareTableTF {
...
    public void compareTable(...) {
    ...
        while (...) {
        try {
            myTask = new CompareTableTask(...);
            completion.submit(myTask);
        }
        catch (InterruptedException | ExecutionException e) {...}
        ...
    }
}
public class CompareTableTask implements Callable<Integer> {
    private List<Object> row1;

    public CompareTableTask(List<Object> row1) { 
        this.row1=row1;
    }

    @Override
    public Integer call() {
        int result=1;
        List<Object> row2=getRowToCompare(...);
        if (isEqual(row2)) {
            result=0;
        }
        return result;
    }

    private boolean isEqual(List<Object> row2) {
        ...
        for (int i=0; i<n; i++) {
            if (! Tools.areEqual(row1.get(i), row2.get(i))) return false;
        }
        return true;
    }
public class Tools {
    private final static SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.S");

    ...
    public synchronized static boolean areEqual(Object obj1, Object obj2) {
        String sObj1, sObj2;
        Double dObj1, dObj2;
        Date dtObj1, dtObj2;
        boolean resultat = true;
        
        // Check if one only is empty
        ...
        // Compare as objects
        if (!obj1.equals(obj2)) {

        // if differents, compare as strings
            sObj1=obj1.toString();
            sObj2=obj2.toString();
            if (!sObj1.equals(sObj2)) {

            // if differents, verify if are instanceof java.util.Date, convert them to strings, compare as strings
            if (obj1 instanceof Date && obj2 instanceof Date) {
                if (obj1 instanceof Timestamp) {dtObj1 = new Date(((Timestamp) obj1).getTime());} else {dtObj1 = (Date) obj1;}
                if (obj2 instanceof Timestamp) {dtObj2 = new Date(((Timestamp) obj2).getTime());} else {dtObj2 = (Date) obj2;}
                sObj1 = dateTimeFormat.format(dtObj1);
                sObj2 = dateTimeFormat.format(dtObj2);
                result = sObj1.equals(sObj2);
            }
            else {
                // if differents (and not date), last compare as Double
                resultat=false;
                try {
                    dObj1 = new Double(sObj1);
                    dObj2 = new Double(sObj2);
                    if (dObj1.equals(dObj2)) {
                        resultat=true;
                    }
                }
                catch (NumberFormatException e) {
                }
            }
        }
        return result;
    }
}
1

There are 1 best solutions below

0
rzwitserloot On

JDBC's API is semi-broken in that it guarantees the existence of getTimestamp/setTimestamp, based on java.sql.Timestamp, as well as setDate/getDate, based on java.sql.Date. Both of those types extend java.util.Date which is unfortunate; all these types are broken.

JDBC as a design has decided that making a getFoobar() method for every relevant data type is no longer a scalable proposition. The new JDBC way is to use the any-typed get and set methods:

try (ResultSet rs = ....) {
  LocalDate ld = rs.getObject(1, LocalDate.Class); // right
  java.sql.Date d = rs.getDate(1); // wrong
}

LocalDate and OffsetDateTime are all guaranteed to work. LocalDateTime and ZonedDateTime depends on your JDBC implementation. For example, Postgres JDBC driver suports LocalDate, LocalTime, LocalDateTime, and OffsetDateTime. Which are the java equivalents for, respectively, DATE, TIME WITHOUT TIME ZONE, TIMESTAMP WITHOUT TIME ZONE, and TIMESTAMP WITH TIME ZONE. And TIME and TIMESTAMP are aliases for the without time zone variants.

For PreparedStatements, prepStatement.setObject(1, localDateInstance) is how its done (there is no setLocalDate method, nor will there ever be - JDBC design has gone with setObject for all future types relevant to DB interactions).

Crucially all these classes, from the java.time package, aren't broken, and match how DBs work, which is to store human reckoning terms directly. E.g. a DATE stores, in the database, 3 numbers: a year, a month, and a day. java.time.LocalTime works the same way. java.sql.Date (and java.util.Date, the class it extends) do not which makes them very very evil classes because they are blatant lies. They store millis passed since the epoch and attempt to translate this into dates using some math which means timezone issues can mess this up. And just about every place on the planet has changed some aspect of time zones one way or another (such as shifting the date when winter time or summmer time kicks in), which is why it's so perniciously bad to rely on timezone info: It seems to work fine, until it doesn't. Bugs that are not easily testable are really bad bugs. Which is why it is so very very important you do not ever use java.util.Date and all its evil offspring, such as java.sql.Date and java.sql.Timestamp.

Once you have java.time objects: They have .isEqual methods, their formatting and parsing tooling is thread safe, and so on. Your problems will disappear.