Immutable phase 7 of shared model

Posted by coja1 on Tue, 21 Sep 2021 10:28:19 +0200

7.1 date conversion

The problem is that the following code has a high probability of java.lang.NumberFormatException or incorrect date resolution results when running because SimpleDateFormat is not thread safe.

 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 probability of java.lang.NumberFormatException or incorrect date resolution results, such as:

  Solution

Scheme 1: locking

  Scheme 2: use DateTimeFormatter

Idea - immutable object

If an object cannot modify its internal state (properties), 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 DateTimeFormatter 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();
        }

7.2 immutable design

  1. For more information about immutable classes, refer to this here
  2. final class knowledge, reference here

Another more familiar String class is immutable. Take it as an example to illustrate the elements of immutable class 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
    // ...
}

Use of final

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

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

Class is decorated with final to ensure that the methods in the class cannot be overwritten and prevent subclasses from inadvertently destroying immutability

defensive copy

String construction method, passing string array, and realizing data security through array copy, which is also a protective copy. If other threads use the same array, the security of the data cannot be guaranteed if other threads modify the data.

However, some students will say that when using strings, there are also some methods related to modification, such as substring. Let's take substring as an example:

 public String substring(int beginIndex, int endIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        if (endIndex > value.length) {
            throw new StringIndexOutOfBoundsException(endIndex);
        }
        int subLen = endIndex - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        // The above is some verification, and the following is the real creation of a new String object
        return ((beginIndex == 0) && (endIndex == value.length)) ? this
                : new String(value, beginIndex, subLen);
    }

It is found that it calls the String construction method to create a new String. Then enter this construction to see if the final char[] value has been modified: it is found that there is no change. When constructing a new String object, a new char[] value will be generated to copy the content. This method of avoiding sharing by creating replica objects is called [protective copy]

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;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        // The above is some security verification. The following is to assign a value to the String object. A new array is created to save the value of the String object
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

Enjoy yuan of 23 design modes

Friendly note: These are 23 design patterns, not thread patterns, and this is mainly used. When there is a reused object, you can directly use the object, just like the constant pool variable of String. This object has the function of directly using the constant pool in the constant pool.

  1. Introduction definition English Name: Flyweight pattern. When a limited number of objects of the same type need to be reused

  2. reflect

    1. In JDK, wrapper classes such as Boolean, Byte, Short, Integer, Long, and Character provide valueOf methods. For example, valueOf of Long caches Long objects between - 128 and 127. Objects will be reused in this range. If the range is greater than this, Long objects will be created:

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

Note: the range of byte, short and long caches is - 128127. The range of character cache is 0127. The default range of integer is - 128127. The minimum value cannot be changed, but the maximum value can be adjusted by adjusting the virtual machine parameter "- Djava.lang.Integer.IntegerCache.high"

To change the Boolean cache to TRUE and FALSE

  • String string pool: thread safe

  • BigDecimal BigInteger: thread safe

DIY custom connection pool

Friendly tip: the reference is the implementation of tomcat database connection pool

For example, an online mall application has thousands of QPS. If the database connection is re created and closed every time, the performance will be greatly affected. At this time, create a batch of connections in advance and put them into the connection pool. After a request arrives, the connection is obtained from the connection pool and 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 as not to let the huge number of connections crush the database.

package cn.itcast.n7;


import lombok.extern.slf4j.Slf4j;

import java.sql.*;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicIntegerArray;

public class Test3 {
    public static void main(String[] args) {
        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();
        }
    }
}

@Slf4j(topic = "c.Pool")
class Pool {
    // 1. Connection pool size
    private final int poolSize;

    // 2. Connection object array
    private Connection[] connections;

    // 3. The connection status array 0 indicates idle and 1 indicates busy
    private AtomicIntegerArray states;

    // 4. Initialization of construction method
    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("connect" + (i+1));
        }
    }

    // 5. Borrow connection
    public Connection borrow() {
        while(true) {
            for (int i = 0; i < poolSize; i++) {
                // Get 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 wait
            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);
                synchronized (this) {
                    log.debug("free {}", conn);
                    this.notifyAll();
                }
                break;
            }
        }
    }
}

class MockConnection implements Connection {

    private String name;

    public MockConnection(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "MockConnection{" +
                "name='" + name + '\'' +
                '}';
    }

    @Override
    public Statement createStatement() throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql) throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql) throws SQLException {
        return null;
    }

    @Override
    public String nativeSQL(String sql) throws SQLException {
        return null;
    }

    @Override
    public void setAutoCommit(boolean autoCommit) throws SQLException {

    }

    @Override
    public boolean getAutoCommit() throws SQLException {
        return false;
    }

    @Override
    public void commit() throws SQLException {

    }

    @Override
    public void rollback() throws SQLException {

    }

    @Override
    public void close() throws SQLException {

    }

    @Override
    public boolean isClosed() throws SQLException {
        return false;
    }

    @Override
    public DatabaseMetaData getMetaData() throws SQLException {
        return null;
    }

    @Override
    public void setReadOnly(boolean readOnly) throws SQLException {

    }

    @Override
    public boolean isReadOnly() throws SQLException {
        return false;
    }

    @Override
    public void setCatalog(String catalog) throws SQLException {

    }

    @Override
    public String getCatalog() throws SQLException {
        return null;
    }

    @Override
    public void setTransactionIsolation(int level) throws SQLException {

    }

    @Override
    public int getTransactionIsolation() throws SQLException {
        return 0;
    }

    @Override
    public SQLWarning getWarnings() throws SQLException {
        return null;
    }

    @Override
    public void clearWarnings() throws SQLException {

    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException {
        return null;
    }

    @Override
    public Map<String, Class<?>> getTypeMap() throws SQLException {
        return null;
    }

    @Override
    public void setTypeMap(Map<String, Class<?>> map) throws SQLException {

    }

    @Override
    public void setHoldability(int holdability) throws SQLException {

    }

    @Override
    public int getHoldability() throws SQLException {
        return 0;
    }

    @Override
    public Savepoint setSavepoint() throws SQLException {
        return null;
    }

    @Override
    public Savepoint setSavepoint(String name) throws SQLException {
        return null;
    }

    @Override
    public void rollback(Savepoint savepoint) throws SQLException {

    }

    @Override
    public void releaseSavepoint(Savepoint savepoint) throws SQLException {

    }

    @Override
    public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException {
        return null;
    }

    @Override
    public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException {
        return null;
    }

    @Override
    public Clob createClob() throws SQLException {
        return null;
    }

    @Override
    public Blob createBlob() throws SQLException {
        return null;
    }

    @Override
    public NClob createNClob() throws SQLException {
        return null;
    }

    @Override
    public SQLXML createSQLXML() throws SQLException {
        return null;
    }

    @Override
    public boolean isValid(int timeout) throws SQLException {
        return false;
    }

    @Override
    public void setClientInfo(String name, String value) throws SQLClientInfoException {

    }

    @Override
    public void setClientInfo(Properties properties) throws SQLClientInfoException {

    }

    @Override
    public String getClientInfo(String name) throws SQLException {
        return null;
    }

    @Override
    public Properties getClientInfo() throws SQLException {
        return null;
    }

    @Override
    public Array createArrayOf(String typeName, Object[] elements) throws SQLException {
        return null;
    }

    @Override
    public Struct createStruct(String typeName, Object[] attributes) throws SQLException {
        return null;
    }

    @Override
    public void setSchema(String schema) throws SQLException {

    }

    @Override
    public String getSchema() throws SQLException {
        return null;
    }

    @Override
    public void abort(Executor executor) throws SQLException {

    }

    @Override
    public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException {

    }

    @Override
    public int getNetworkTimeout() throws SQLException {
        return 0;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}

  Operation results

  Existing problems:

Principle of final

  1. How to set the final variable

You can see that final adds a write barrier

2. How to get the final variable

Friendly tip: check bytecode

  Stateless

When learning in the web stage, in order to ensure its thread safety when designing a Servlet, it is recommended not to set member variables for the Servlet. This class without any member variables is thread safe

Because the data saved by member variables can also be called status information, it is called stateless if there is no member variable

  • 7.3 summary of this chapter

  • Use of immutable classes
  • Immutable class design
  • Principle: final
  • Mode aspect
    1. Meta mode - > set thread pool

Topics: Java