1. Use of immutable classes
There are the following codes
import lombok.extern.slf4j.Slf4j; import java.text.ParseException; import java.text.SimpleDateFormat; @Slf4j(topic = "c.DateFormatMutable") public class DateFormatMutable { public static void main(String[] args) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); for (int i = 0; i < 10; i++) { new Thread(() -> { try { log.debug("{}",sdf.parse("2021-10-01")); } catch (ParseException e) { log.error("{}", e); } }).start(); } } } // test result Exception in thread "Thread-4" Exception in thread "Thread-3" Exception in thread "Thread-1" Exception in thread "Thread-2" java.lang.NumberFormatException: multiple points
resolvent:
- Locking: it can be realized, but it has performance problems
- Use immutable class: DateTimeFormatter
The code is as follows:
import lombok.extern.slf4j.Slf4j; import java.time.format.DateTimeFormatter; @Slf4j(topic = "c.DateFormatMutable") public class DateFormatImmutable { public static void main(String[] args) { DateTimeFormatter sdf = DateTimeFormatter.ofPattern("yyyy-MM-dd"); for (int i = 0; i < 10; i++) { new Thread(() -> { log.debug("{}",sdf.parse("2021-10-01")); }).start(); } } } // test result [INFO] --- exec-maven-plugin:3.0.0:exec (default-cli) @ concurrent --- 2021-10-01 10:26:04.363 DEBUG [Thread-8] c.DateFormatMutable - {},ISO resolved to 2021-10-01 2021-10-01 10:26:04.363 DEBUG [Thread-3] c.DateFormatMutable - {},ISO resolved to 2021-10-01 2021-10-01 10:26:04.363 DEBUG [Thread-4] c.DateFormatMutable - {},ISO resolved to 2021-10-01 2021-10-01 10:26:04.363 DEBUG [Thread-2] c.DateFormatMutable - {},ISO resolved to 2021-10-01 2021-10-01 10:26:04.363 DEBUG [Thread-10] c.DateFormatMutable - {},ISO resolved to 2021-10-01 2021-10-01 10:26:04.363 DEBUG [Thread-6] c.DateFormatMutable - {},ISO resolved to 2021-10-01 2021-10-01 10:26:04.363 DEBUG [Thread-1] c.DateFormatMutable - {},ISO resolved to 2021-10-01 2021-10-01 10:26:04.363 DEBUG [Thread-9] c.DateFormatMutable - {},ISO resolved to 2021-10-01 2021-10-01 10:26:04.363 DEBUG [Thread-5] c.DateFormatMutable - {},ISO resolved to 2021-10-01 2021-10-01 10:26:04.363 DEBUG [Thread-7] c.DateFormatMutable - {},ISO resolved to 2021-10-01
Simply look at the source code of the changed class
* @implSpec * This class is immutable and thread-safe. * * @since 1.8
- This class is immutable and thread safe
- New classes introduced in jdk1.8
2. Design of immutable classes
Another familiar String class is immutable. Take it as an example to illustrate immutable elements
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 ... 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); } this.value = Arrays.copyOfRange(value, offset, offset+count); } ... 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); } ... }
-
Use of final
- The attribute in the class 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 overridden to prevent subclasses from inadvertently destroying immutability
-
defensive copy
There are some methods related to modification, such as subString. How is it implemented? See the source code. It is found that the String constructor is called internally to create a new String. When constructing a new String, the corresponding construction method will generate a new char[] value to copy the content.
This means of avoiding sharing by creating a replica object is called a protective copy.
3. Meta model - definition and embodiment
3.1 definition
When an immutable class tries to modify an object, the current object will create a new object to ensure thread safety. However, this brings a problem that too many objects will consume resources and low performance. How to solve it? Immutable classes are usually solved by associating design pattern - shared meta pattern classes.
Definition: a flyweight is an object that minimizes memory usage by sharing as much data as possible with other similar objects.
Applicable: when a limited number of objects of the same class need to be reused
Classification: structural pattern
3.2 embodiment
3.2.1 packaging
In JDK, wrapper classes such as Boolean, Byte, short, Integer, Long, and Character provide valueOf() methods. For example, the valueOf() method of Long caches Long objects between - 128 and 127. Objects will be reused in this range. New Long objects will be created only when the range is greater than this range. The source code is as follows:
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); } private static class LongCache { private LongCache(){} static final Long cache[] = new Long[-(-128) + 127 + 1]; static { for(int i = 0; i < cache.length; i++) cache[i] = new Long(i - 128); } }
tips
- The range of byte, short and long caches is - 128 ~ 127
- Character cache range: 0 ~ 127
- The default cache range of Integer is - 128 ~ 127, and the minimum value is immutable; The single maximum value can be changed by adjusting the virtual machine parameter - Djava.lang.Integer.IntegerCache.high
- Boolean caches TRUE and FALSE
3.2.2 String string pool
Consult the relevant documents by yourself. There is no detailed explanation here. It will be discussed when learning the JVM.
3.2.3,BigDecimal BigInteger
Consult the relevant documents by yourself. There is no detailed explanation here. It will be discussed when learning the JVM.
4. Shared meta mode - immutable thread safety
We say that immutable classes are thread safe, but why did we use AtomicReference to include BigDecimal thread safe in our previous bank withdrawal case? Look at the code
import java.math.BigDecimal; import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiConsumer; /** * CAS Lock free implementation */ public class AccountDecimalCAS implements AccountDecimal { private final AtomicReference<BigDecimal> balance; public AccountDecimalCAS(BigDecimal balance) { this.balance = new AtomicReference<>(balance); } @Override public BigDecimal getBalance() { return balance.get(); } @Override public void withdraw(BigDecimal amount) { balance.getAndUpdate(m -> m.subtract(amount)); } // @Override // public void withdraw(BigDecimal amount) { // while (true) { // BigDecimal prev = balance.get(); // BigDecimal next = prev.subtract(amount); // if (balance.compareAndSet(prev, next)) { // break; // } // } // } }
Observe the annotated part, in which BigDecimal's substract method is atomic and the source code
public BigDecimal subtract(BigDecimal subtrahend) { if (this.intCompact != INFLATED) { if ((subtrahend.intCompact != INFLATED)) { return add(this.intCompact, this.scale, -subtrahend.intCompact, subtrahend.scale); } else { return add(this.intCompact, this.scale, subtrahend.intVal.negate(), subtrahend.scale); } } else { if ((subtrahend.intCompact != INFLATED)) { // Pair of subtrahend values given before pair of // values from this BigDecimal to avoid need for // method overloading on the specialized add method return add(-subtrahend.intCompact, subtrahend.scale, this.intVal, this.scale); } else { return add(this.intVal, this.scale, subtrahend.intVal.negate(), subtrahend.scale); } } } private static BigDecimal add(final long xs, int scale1, final long ys, int scale2) { long sdiff = (long) scale1 - scale2; if (sdiff == 0) { return add(xs, ys, scale1); } else if (sdiff < 0) { int raise = checkScale(xs,-sdiff); long scaledX = longMultiplyPowerTen(xs, raise); if (scaledX != INFLATED) { return add(scaledX, ys, scale2); } else { BigInteger bigsum = bigMultiplyPowerTen(xs,raise).add(ys); return ((xs^ys)>=0) ? // same sign test new BigDecimal(bigsum, INFLATED, scale2, 0) : valueOf(bigsum, scale2, 0); } } else { int raise = checkScale(ys,sdiff); long scaledY = longMultiplyPowerTen(ys, raise); if (scaledY != INFLATED) { return add(xs, scaledY, scale1); } else { BigInteger bigsum = bigMultiplyPowerTen(ys,raise).add(xs); return ((xs^ys)>=0) ? new BigDecimal(bigsum, INFLATED, scale1, 0) : valueOf(bigsum, scale1, 0); } } }
At present, any other single method selected in while is also atomic, but the combination of methods is not atomic operation, so AtomicReference is required to ensure the atomicity of the whole method and realize thread safety.
-
conclusion
- A single method of an immutable class is atomic and thread safe
- However, multiple method combinations are not atomic and require additional ways to ensure thread safety
5. Meta mode - custom connection pool
After learning immutable classes and understanding the meta pattern, we use our knowledge to implement a simple user-defined connection pool.
-
Scenario: 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, obtain a connection from the connection pool; After use, return it to the connection pool. This not only saves time and resources, realizes reuse, can respond to client requests in time, and will not cause too much burden on the database.
-
The code is as follows
import lombok.extern.slf4j.Slf4j; import java.sql.Connection; import java.util.concurrent.atomic.AtomicIntegerArray; /** * Custom thread connection pool */ @Slf4j(topic = "c.Pool") public final class Pool { /** Connection pool size */ private final int poolSize; /** Join array */ private final Connection[] conns; /** Mark whether the connection is occupied, 0-unoccupied, 1-occupied */ private final AtomicIntegerArray busies; /** * Construction method: initialize connection pool * @param poolSize Initial connection pool size */ public Pool(int poolSize) { this.poolSize = poolSize; conns = new Connection[poolSize]; busies = new AtomicIntegerArray(new int[poolSize]); for (int i = 0; i < poolSize; i++) { conns[i] = new MockConnection("conn" + i); } } /** * Get connection from connection pool * @return connect */ public Connection getConnection() { // Check for free connections while (true) { for (int i = 0; i < poolSize; i++) { // There are idle connections and return connections if (busies.get(i) == 0) { // Idle, modify flag, return connection if (busies.compareAndSet(i, 0, 1)) { log.debug("get {}", conns[i]); return conns[i]; } } } // No idle connections waiting synchronized (this) { try { log.debug("wait..."); this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * Return connection * @param conn Connection to return */ public void close(Connection conn) { for (int i = 0; i < poolSize; i++) { if (conns[i] == conn) { // There will be no competition for returned connections busies.set(i, 0); synchronized (this) { log.debug("close {}", conn); this.notifyAll(); } break; } } } } /** * connect */ @Data @NoArgsConstructor @AllArgsConstructor public class MockConnection implements Connection { private String name; ... } import java.sql.Connection; import java.util.concurrent.TimeUnit; /** * Test connection pool */ public class PoolTest { public static void main(String[] args) { Pool p = new Pool(2); for (int i = 0; i < 5; i++) { new Thread(() -> { Connection connection = p.getConnection(); try { // Time consuming for analog connection TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } finally { p.close(connection); } }).start(); } } } // test result 2021-10-01 11:33:32.443 DEBUG [Thread-3] c.Pool - wait... 2021-10-01 11:33:32.443 DEBUG [Thread-1] c.Pool - get MockConnection(name=conn0) 2021-10-01 11:33:32.443 DEBUG [Thread-2] c.Pool - get MockConnection(name=conn1) 2021-10-01 11:33:32.445 DEBUG [Thread-5] c.Pool - wait... 2021-10-01 11:33:32.445 DEBUG [Thread-4] c.Pool - wait... 2021-10-01 11:33:33.445 DEBUG [Thread-2] c.Pool - close MockConnection(name=conn1) 2021-10-01 11:33:33.445 DEBUG [Thread-4] c.Pool - get MockConnection(name=conn0) 2021-10-01 11:33:33.445 DEBUG [Thread-5] c.Pool - get MockConnection(name=conn1) 2021-10-01 11:33:33.445 DEBUG [Thread-3] c.Pool - wait... 2021-10-01 11:33:33.445 DEBUG [Thread-1] c.Pool - close MockConnection(name=conn0) 2021-10-01 11:33:33.445 DEBUG [Thread-3] c.Pool - wait... 2021-10-01 11:33:34.445 DEBUG [Thread-4] c.Pool - close MockConnection(name=conn0) 2021-10-01 11:33:34.445 DEBUG [Thread-3] c.Pool - get MockConnection(name=conn0) 2021-10-01 11:33:34.445 DEBUG [Thread-5] c.Pool - close MockConnection(name=conn1) 2021-10-01 11:33:35.445 DEBUG [Thread-3] c.Pool - close MockConnection(name=conn0)
Question:
- Dynamic increase and decrease of connection pool
- Connection keep alive (availability test)
- Wait for timeout processing
- Distributed hash
- ...
In practical application, we should use the mature connection pool to implement. Relational databases, such as C3P0, druid, etc. for more general object connection pool, we can consider apache commons pool, etc. when we learn about the framework in the future, we can see how the mature connection is implemented (source code) when we have time.
6. final principle
6.1 principle of setting final variable
After understanding the volatile principle, the implementation of final is simple compared with simple examples and bytecode
public class FinalTest { final int a = 20; } // class version 52.0 (52) // access flags 0x21 public class com/gaogzhen/final01/FinalTest { // compiled from: FinalTest.java // access flags 0x10 final I a = 20 // access flags 0x1 public <init>()V L0 LINENUMBER 3 L0 ALOAD 0 INVOKESPECIAL java/lang/Object.<init> ()V L1 LINENUMBER 4 L1 ALOAD 0 BIPUSH 20 PUTFIELD com/gaogzhen/final01/FinalTest.a : I <-- Write barrier RETURN L2 LOCALVARIABLE this Lcom/gaogzhen/final01/FinalTest; L0 L2 0 MAXSTACK = 2 MAXLOCALS = 1 }
It is found that the assignment of the final variable will also be completed through the putfield instruction. Similarly, a write barrier will be added after this instruction to ensure that other threads will not read its value as 0.
6.2 principle of obtaining final variable
final read optimization
- Smaller value: copy a copy to the stack memory to read
- Larger value: constant pool
- Read heap memory without final, low performance
7. Stateless
When learning in the web stage and designing a Servlet, in order to ensure its thread safety, there will be such suggestions. Do not 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 state information, no member variables are called stateless.
QQ:806797785
Warehouse address: https://gitee.com/gaogzhen/concurrent