preface
Start with (6) and discuss some immutable classes. The article is based on the book "the art of Java Concurrent Programming" and the video of dark horse Dark horse multithreading Take notes.
1. Date conversion
For the date class SimpleDateFormat, since SimpleDateFormat is not thread safe, exceptions may occur in the use of SimpleDateFormat in the case of multithreading. The following is an example:
private static void test1() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); //Define 10 threads for parsing for (int i = 0; i < 10; i++) { new Thread(() -> { try { log.debug("{}", sdf.parse("2021-2-4")); } catch (Exception e) { log.error("{}", e); } }).start(); } }
java.lang.NumberFormatException: For input string: "202120212021E" java.lang.NumberFormatException: For input string: "" java.lang.NumberFormatException: multiple points java.lang.NumberFormatException: multiple points
Multithreading is not safe. Of course, we can use synchronized to synchronize threads, but there is a problem that the efficiency of the program will be reduced
private static void test2() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); for (int i = 0; i < 10; i++) { new Thread(() -> { synchronized (sdf) { try { log.debug("{}", sdf.parse("1951-04-21")); } catch (Exception e) { log.error("{}", e); } } }).start(); } }
JDK 8 provides a new date formatting class, DateTimeFormatter. This class is thread safe internally and its internal state cannot be modified. Therefore, we use DateTimeFormatter to replace SimpleDateFormat. Here is the code:
private static void test3() { DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd"); for (int i = 0; i < 10; i++) { new Thread(() -> { //LocalDate.from method to create a localdate instance LocalDate date = dtf.parse("2018-10-01", LocalDate::from); log.debug("{}", date); }).start(); } }
Through the above example, we can draw a conclusion: the internal design of the class is immutable, which is also a thread safe method
2. Immutable design
1. Design of string class: protective copy
Let's take the String class as an example to explain how String is immutable. Just find two methods
1,subString
It can be seen from the following method that the last returned String is a new String, which is created through new String. Note that the new String is stored on the heap when it comes out, so it can be seen from this method that the original String is not modified, but a new String is created. It can also be seen from the construction method that a new array is created internally to store the String value, so the original String has not changed. Even in the case of multithreading, thread safety will only be reflected in the creation of multiple String objects without modifying the original.
public String substring(int beginIndex) { if (beginIndex < 0) { throw new StringIndexOutOfBoundsException(beginIndex); } int subLen = value.length - beginIndex; if (subLen < 0) { throw new StringIndexOutOfBoundsException(subLen); } //The key here is that you can see that new creates a new string return (beginIndex == 0) ? this : new String(value, beginIndex, subLen); } 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); } //The copyOfRange method is used here. See the following method this.value = Arrays.copyOfRange(value, offset, offset+count); } public static char[] copyOfRange(char[] original, int from, int to) { int newLength = to - from; if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); //A new array is created internally to store the old content char[] copy = new char[newLength]; System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); return copy; }
2,concat
You can see that the new String method is also used internally to create a new String. The principle is not explained
public String concat(String str) { int otherLen = str.length(); if (otherLen == 0) { return this; } int len = value.length; char buf[] = Arrays.copyOf(value, len + otherLen); str.getChars(buf, len); return new String(buf, true); }
2. Design of final class
It is found that the class and all attributes in the class are final. Some basic knowledge of final can be seen here On 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 this class cannot be overwritten and prevent subclasses from inadvertently destroying immutability
For this knowledge, first give a video link: final principle , the final class is also introduced in the art of Java Concurrent Programming. A separate article will be written to introduce the memory semantics of final.
About the writing of the final variable: in short, a memory barrier will be added to prevent other threads from reading the variable before the assignment is completed.
On the writing of final variables: look at the following code and analyze it from the bytecode level
public class TestFinal { //Two static members become tired static int A = 10; static int B = Short.MAX_VALUE+1; //Two ordinary final become tired final int a = 20; final int b = Integer.MAX_VALUE; final void test1() { final int c = 30; new Thread(()->{ System.out.println(c); }).start(); final int d = 30; class Task implements Runnable { @Override public void run() { System.out.println(d); } } new Thread(new Task()).start(); } } class UseFinal1 { public void test() { System.out.println(TestFinal.A); //1 System.out.println(TestFinal.B); //2 System.out.println(new TestFinal().a); //3 System.out.println(new TestFinal().b); //4 new TestFinal().test1(); } } class UseFinal2 { public void test() { System.out.println(TestFinal.A); } }
The following are the operations on 1 and 2 at the bytecode level
L0 LINENUMBER 33 L0 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; //This instance actually copies a copy of final to the stack, and then obtains the value in the stack //If final is not added, this instruction is GETSTATIC BIPUSH 10 INVOKEVIRTUAL java/io/PrintStream.println (I)V L1 LINENUMBER 34 L1 GETSTATIC java/lang/System.out : Ljava/io/PrintStream; //Read the contents of the constant pool. After JDK8, it is in the heap LDC 32768 INVOKEVIRTUAL java/io/PrintStream.println (I)V
In fact, one of the characteristics reflected in the above bytecode is that the variable modified by final is determined at the beginning and cannot be modified later. When you want to read, you can see that the ordinary value is copied to the stack for reading, while the maximum value is moved to the heap.
3. Yuan sharing mode
Definition: When you need to reuse a limited number of objects of the same class, you can use the meta pattern
In fact, it is to use the already created object instead of using the new object.
1. Embodiment
-
In JDK, wrapper classes such as Boolean, Byte, Short, Integer, Long and Character provide valueOf methods
-
Note: in the valueOf method, not all values in the range are reused, but only in a certain range. We can look at the source code and use short valueOf () as an example, you can see that it can only be reused between [- 128 ~ 127], and beyond this object, it cannot be reused.
-
The range of byte, short and long caches 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 changed by adjusting the virtual machine parameter - "Djava.lang.Integer.IntegerCache.high"
-
Boolean caches TRUE and FALSE
-
As for why it is - 128 - 127, it may be that it is used more times in this interval. According to the thinking of writing items, some commonly used items are cached
-
-
String string pool
-
BigDecimal BigInteger (new is also used internally to create a new one to protect the old one)
However: thread safety of immutable classes means that a single function is thread safe, and it is not thread safe to combine or call multiple methods. For example, when testing BigDecimal, an atomic reference class is used to protect it.
2. Customize a connection pool
1. Parameter design and construction method
//1. Specify the connection pool size private final int poolSize; //2. Array of connected objects private Connection[] connections; //3. Connection status array 0 idle 1 busy private AtomicIntegerArray status; //4. The constructor initializes the attribute public Pool(int poolSize){ this.poolSize = poolSize; this.connections = new Connection[poolSize]; this.status = new AtomicIntegerArray(new int[poolSize]); for(int i = 0; i < poolSize; i++){ connections[i] = new MorkConnection("connect-" + (i + 1)); } }
2. Connection method
Using the wait - notifyAll method, when a thread cannot get a connection, it uses wait to wait. In fact, it is better to use park - unpark here, because using wait - notifyAll may occur when notifyAll is performed before wait
//5. Borrow connection public Connection borrow(){ while(true){ for(int i = 0; i < poolSize; i++){ //Got free connection if(status.get(i) == 0){ //Take out a connection and set the state of the corresponding subscript to 1 if(status.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(); } } } }
3. Return the connection
CAS operation is not required when returning the connection, because if connections[i] == connection is judged to be true, it means that the connection of the current thread corresponds to a subscript, and other threads have other connections, which are different, so there will be no two threads entering here to set I to 0;
//6. Return the connection public void free(Connection connection){ for(int i = 0; i < poolSize; i++){ if(connections[i] == connection){ status.set(i, 0); synchronized (this){ log.debug("free:{}", connection); this.notifyAll(); } break; } } }
4. All codes
@Slf4j class Pool{ //1. Specify the connection pool size private final int poolSize; //2. Array of connected objects private Connection[] connections; //3. Connection status array 0 idle 1 busy private AtomicIntegerArray status; //4. The constructor initializes the attribute public Pool(int poolSize){ this.poolSize = poolSize; this.connections = new Connection[poolSize]; this.status = new AtomicIntegerArray(new int[poolSize]); for(int i = 0; i < poolSize; i++){ connections[i] = new MorkConnection("connect-" + (i + 1)); } } //5. Borrow connection public Connection borrow(){ while(true){ for(int i = 0; i < poolSize; i++){ //Got free connection if(status.get(i) == 0){ if(status.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 connection){ for(int i = 0; i < poolSize; i++){ if(connections[i] == connection){ status.set(i, 0); synchronized (this){ log.debug("free:{}", connection); this.notifyAll(); } break; } } } } //Connection, we only need to use it as a connection, and the methods inside do not need to be rewritten class MorkConnection implements Connection{ private String name; public MorkConnection(String name) { this.name = name; } @Override public String toString() { return "MorkConnection{" + "name='" + name + '\'' + '}'; } //Omit a lot of rewriting methods }
5. Test results
Poolsize pool = new Poolsize(2); for(int i = 0; i < 5; i ++){ new Thread(()->{ Connection connection = pool.borrow(); try { Thread.sleep(new Random().nextInt(1000)); } catch (InterruptedException e) { e.printStackTrace(); }finally { pool.free(connection); } }).start(); }
//1. 15:29:13.226 [Thread-1] DEBUG cn.itcast.n7.Poolsize - wait... 15:29:13.226 [Thread-2] DEBUG cn.itcast.n7.Poolsize - borrow:MorkConnection{name='connect-2'} 15:29:13.231 [Thread-4] DEBUG cn.itcast.n7.Poolsize - wait... 15:29:13.231 [Thread-3] DEBUG cn.itcast.n7.Poolsize - wait... 15:29:13.226 [Thread-0] DEBUG cn.itcast.n7.Poolsize - borrow:MorkConnection{name='connect-1'} The above shows that thread 2 and thread 0 have obtained the lock, and then the other three threads have not obtained it and are waiting //2. //Thread 0 releases the lock 15:29:13.552 [Thread-0] DEBUG cn.itcast.n7.Poolsize - free:MorkConnection{name='connect-1'} //Thread 3 gets lock get lock 15:29:13.552 [Thread-3] DEBUG cn.itcast.n7.Poolsize - borrow:MorkConnection{name='connect-1'} 15:29:13.552 [Thread-4] DEBUG cn.itcast.n7.Poolsize - wait... 15:29:13.552 [Thread-1] DEBUG cn.itcast.n7.Poolsize - wait... //At this time, thread 4 and thread 1 are still waiting //3. //Thread 2 released the lock 15:29:13.929 [Thread-2] DEBUG cn.itcast.n7.Poolsize - free:MorkConnection{name='connect-2'} 15:29:13.929 [Thread-4] DEBUG cn.itcast.n7.Poolsize - wait... //Thread 1 acquired the lock 15:29:13.929 [Thread-1] DEBUG cn.itcast.n7.Poolsize - borrow:MorkConnection{name='connect-2'} //Thread 4 is still waiting //4. //Thread 1 release lock 15:29:14.348 [Thread-1] DEBUG cn.itcast.n7.Poolsize - free:MorkConnection{name='connect-2'} //Thread 4 gets lock 15:29:14.348 [Thread-4] DEBUG cn.itcast.n7.Poolsize - borrow:MorkConnection{name='connect-2'} //Finally, thread 3 releases the lock 15:29:14.355 [Thread-3] DEBUG cn.itcast.n7.Poolsize - free:MorkConnection{name='connect-1'} //Thread 4 release lock 15:29:14.797 [Thread-4] DEBUG cn.itcast.n7.Poolsize - free:MorkConnection{name='connect-2'} Process finished with exit code 0
If there is any error, please point it out!!!