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
- For more information about immutable classes, refer to this here
- 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.
-
Introduction definition English Name: Flyweight pattern. When a limited number of objects of the same type need to be reused
-
reflect
-
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
-
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
- Meta mode - > set thread pool