[Concurrent Programming] [13] [Shared Model Immutable] Date conversion problem Immutable design Stateless

7. Shared model immutability

What this chapter is about

  • Use of immutable classes

  • immutable class design

  • stateless class design

7.1 Date conversion issues

Ask a question

When the following code runs, due to SimpleDateFormat not thread-safe (mutable class)

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i ++ ) {<!-- -->
    new Thread(() -> {<!-- -->
        try {<!-- -->
            log.debug("{}", sdf.parse("1951-04-21"));
        } catch (Exception e) {<!-- -->
            log.error("{}", e);
        }
    }).start();
}

There is a high chance of java.lang.NumberFormatException or incorrect date parsing results, for example:

19:10:40.859 [Thread-2] c.TestDateParse - {}
java.lang.NumberFormatException: For input string: ""
 at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
 at java.lang.Long.parseLong(Long.java:601)
 at java.lang.Long.parseLong(Long.java:631)
 at java.text.DigitList.getLong(DigitList.java:195)
 at java.text.DecimalFormat.parse(DecimalFormat.java:2084)
 at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
 at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
 at java.text.DateFormat.parse(DateFormat.java:364)
 at cn.itcast.n7.TestDateParse.lambda$test1$0(TestDateParse.java:18)
 at java.lang.Thread.run(Thread.java:748)
19:10:40.859 [Thread-1] c.TestDateParse - {}
java.lang.NumberFormatException: empty String
 at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
 at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
 at java.lang.Double.parseDouble(Double.java:538)
 at java.text.DigitList.getDouble(DigitList.java:169)
 at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
 at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
 at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
 at java.text.DateFormat.parse(DateFormat.java:364)
 at cn.itcast.n7.TestDateParse.lambda$test1$0(TestDateParse.java:18)
 at java.lang.Thread.run(Thread.java:748)
19:10:40.857 [Thread-8] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951
19:10:40.857 [Thread-9] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951
19:10:40.857 [Thread-6] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951
19:10:40.857 [Thread-4] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951
19:10:40.857 [Thread-5] c.TestDateParse - Mon Apr 21 00:00:00 CST 178960645
19:10:40.857 [Thread-0] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951
19:10:40.857 [Thread-7] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951
19:10:40.857 [Thread-3] c.TestDateParse - Sat Apr 21 00:00:00 CST 1951

Thoughts Synclock

Although this can solve the problem, it brings a performance loss, which is not very good:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 50; i ++ ) {<!-- -->
    new Thread(() -> {<!-- -->
        synchronized (sdf) {<!-- -->
            try {<!-- -->
                log.debug("{}", sdf.parse("1951-04-21"));
            } catch (Exception e) {<!-- -->
                log.error("{}", e);
            }
        }
    }).start();
}

Thoughts Immutable

If an object cannot modify its internal state (properties), then it is thread-safe, because there is no concurrent modification! There are many such objects in Java. For example, after Java 8, a new date formatting class is provided:

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i ++ ) {<!-- -->
    new Thread(() -> {<!-- -->
        LocalDate date = dtf. parse("2018-10-01", LocalDate::from);
        log.debug("{}", date);
    }).start();
}

See the documentation for DateTimeFormatter:

@implSpec
This class is immutable and thread-safe.

Immutable objects are actually another way to avoid races.

7.2 Immutable Design

Another String class that everyone is more familiar with is also immutable. Take it as an example to explain the elements of immutable design

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {<!-- -->
    /** The value is used for character storage. */
    private final char value[];
    
    /** Cache the hash code for the string */
    private int hash; // Default to 0

    //...

}

final use

It is found that the class and all properties in the class are final

  • The attribute is modified with final to ensure that the attribute is read-only and cannot be modified

  • The final modification of the class ensures that the methods in the class cannot be overridden, preventing subclasses from inadvertently destroying immutability

Protective copy

But some students will say that when using strings, there are also some methods related to modification, such as substring, etc. Then let’s take a look at how these methods are implemented, taking substring as an example:

public String substring(int beginIndex) {<!-- -->
    if (beginIndex < 0) {<!-- -->
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value. length - beginIndex;
    if (subLen < 0) {<!-- -->
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

It is found that a new string is created by calling the constructor of String, and then enter this structure to see if the final char[] value has been modified:

public String(char value[], int offset, int count) {<!-- -->
    if (offset < 0) {<!-- -->
        throw new StringIndexOutOfBoundsException(offset);
    }
    if (count <= 0) {<!-- -->
        if (count < 0) {<!-- -->
            throw new StringIndexOutOfBoundsException(count);
        }
        if (offset <= value. length) {<!-- -->
            this.value = "".value;
            return;
        }
    }
    if (offset > value. length - count) {<!-- -->
        throw new StringIndexOutOfBoundsException(offset + count);
    }
    this.value = Arrays.copyOfRange(value, offset, offset + count);
}

It turns out that there is no such thing. When constructing a new string object, a new char[] value will be generated and the content will be copied. This method of avoiding sharing by creating a copy object is called [protective copy (defensive copy)].

* Model Flyweight

1. Introduction

Definition English name: Flyweight pattern. When a limited number of objects of the same type need to be reused

wikipedia: A flflyweight is an object that minimizes memory usage by sharing as much data as possible with other similar objects

From

“Gang of Four” design patterns

Categorize

Structural patterns

2. Reveal

2.1 Wrapper class

In JDK, wrapper classes such as Boolean, Byte, Short, Integer, Long, and Character provide the valueOf method. For example, the valueOf of Long will cache Long objects between -128 and 127. Between this range The object will be reused, and the Long object will be created if it is larger than this range:

public static Long valueOf(long l) {<!-- -->
    final int offset = 128;
    if (l >= -128 & amp; & amp; l <= 127) {<!-- --> // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}

Note:

  • Byte, Short, Long cache range is -128~127

  • The range of Character cache is 0~127

  • The default range of Integer is -128~127

  • The minimum value cannot be changed

  • But the maximum value can be adjusted by adjusting the virtual machine parameters

    -Djava.lang.Integer.IntegerCache.high to change

  • Boolean cached TRUE and FALSE

2.2 String String Pool
2.3 BigDecimal BigInteger

But there is no guarantee that the combination of multiple methods of these classes is thread-safe

3. DIY (Do it yourself)

For example: an online shopping mall application, with thousands of QPS, if the database connection is recreated and closed every time, the performance will be greatly affected. At this time, a batch of connections is pre-created and put into the connection pool. After a request arrives, the connection is obtained from the connection pool, and then returned to the connection pool after use, which not only saves the creation and closing time of the connection, but also realizes the reuse of the connection, so that the huge number of connections will not overwhelm the database. (flyweight mode)

class Pool {<!-- -->
    // 1. Connection pool size
    private final int poolSize;
    
    // 2. Connect the array of objects
    private Connection[] connections;
    
    // 3. Connection status array 0 means idle, 1 means busy
    private AtomicIntegerArray states;
    
    // 4. Constructor initialization
    public Pool(int poolSize) {<!-- -->
        this. poolSize = poolSize;
        this.connections = new Connection[poolSize];
        this.states = new AtomicIntegerArray(new int[poolSize]);
        for (int i = 0; i < poolSize; i ++ ) {<!-- -->
            connections[i] = new MockConnection("Connection" + (i + 1));
        }
    }
    
    // 5. Borrow connection
    public Connection borrow() {<!-- -->
        while(true) {<!-- -->
            for (int i = 0; i < poolSize; i ++ ) {<!-- -->
                // Get an idle connection
                if(states.get(i) == 0) {<!-- -->
                    if (states. compareAndSet(i, 0, 1)) {<!-- -->
                        log.debug("borrow {}", connections[i]);
                        return connections[i];
                    }
                }
            }
            // If there is no idle connection, the current thread enters waiting
            synchronized (this) {<!-- -->
                try {<!-- -->
                    log.debug("wait...");
                    this. wait();
                } catch (InterruptedException e) {<!-- -->
                    e.printStackTrace();
                }
            }
        }
    }
    
    // 6. Return the connection
    public void free(Connection conn) {<!-- -->
        for (int i = 0; i < poolSize; i ++ ) {<!-- -->
            if (connections[i] == conn) {<!-- -->
                states.set(i, 0); // return is the thread currently holding the connection pool, there is no competition
                synchronized (this) {<!-- -->
                    log.debug("free {}", conn);
                    this. notifyAll();
                }
                break;
            }
        }
    }
}

class MockConnection implements Connection {<!-- -->
    // implementation slightly
}

Use connection pool:

Pool pool = new Pool(2);
for (int i = 0; i < 5; i ++ ) {<!-- -->
    new Thread(() -> {<!-- -->
        Connection conn = pool.borrow();
        try {<!-- -->
            Thread. sleep(new Random(). nextInt(1000));
        } catch (InterruptedException e) {<!-- -->
            e.printStackTrace();
        }
        pool. free(conn);
    }).start();
}

The above implementation does not take into account:

  • Dynamic growth and contraction of connections

  • Connection keep alive (availability detection)

  • Waiting for timeout processing

  • Distributed hash

For relational databases, there are relatively mature connection pool implementations, such as c3p0, druid, etc.

For a more general object pool, you can consider using apache commons pool, for example, redis connection pool can refer to the implementation of connection pool in jedis

* Principle final

1. Setting final The principle of variables

After understanding the principle of volatile, it is relatively simple to compare the implementation of final

public class TestFinal {<!-- -->
    final int a = 20;
}

bytecode

0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 20
7: putfield #2 // Field a:I
 <-- write barrier
10: return

It is found that the assignment of the final variable will also be done through the putfield instruction, and a write barrier will also be added after this instruction to ensure that it will not be 0 when other threads read its value

2. Acquisition final The principle of variables

When using final, the method will copy the value to the method stack or constant pool. If no method is added, it will be obtained from the heap, and the performance will be lower

7.3 Statusless

When studying in the web stage, in order to ensure its thread safety when designing Servlet, there will be such suggestions, do not set member variables for Servlet, this kind of class without any member variables is thread safe

Because the data saved by member variables can also be called state information, so without member variables it is called [stateless]

Chapter Summary

  • Immutable classes use

  • immutable class design

  • * Principle

    • final (member variable, static member variable)
  • mode

    • Enjoy Yuan