The immutability of the JUC concurrent programming sharing model (6)

6.1 Problems with date conversion

SimpleDateFormat is not thread-safe

@Slf4j
public class SimpleDateFormatTest {<!-- -->
    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("1951-04-21"));
                } catch (Exception e) {<!-- -->
                    log.error("{}", e);
                }
            }).start();
        }
    }
}

Incorrect date parsing results will appear

Lock SimpleDateFormat, but performance loss

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

Immutable time classes (thread-safe)

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

@Slf4j
public class DateTimeFormatterTest01 {<!-- -->

    public static void main(String[] args) {<!-- -->
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        for(int i = 0; i < 10; i ++ ){<!-- -->
            new Thread(() -> {<!-- -->
                LocalDate localDate = dtf. parse("2018-03-24", LocalDate::from);
                log.debug("{}", localDate);
            }).start();
        }
    }
}

  • Immutable + Thread Safety

6.2 Immutable design

Another familiar String class is also immutable, an element 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
    
    // ....
}

The use of final

It is found that all properties in this class and 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

Using the modification method of the string, the construction method of String is called internally to create a new string

 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);
    }
  • When constructing a new string object, a new char[] value will be generated to copy the content. This method of creating a copy object to avoid sharing is called protective copy

6.3 Flyweight mode

6.3.1. Introduction

Flyweight pattern uses sharing technology to effectively support a large number of fine-grained objects. Mainly used to reduce the number of objects created to reduce memory usage and improve performance.

This type of design pattern is a structural pattern, which provides a way to reduce the number of objects and thus improve the object structure required by the application. The Flyweight pattern tries to reuse existing objects of the same kind, and if no matching object is found, a new object is created.

6.3.2. Reflect

Wrapper class

In the JDK, wrapper classes such as Boolean, Byte, Short, Integer, and Character provide valueOf methods. For example, the valueOf of Long will cache Long objects between -128 and 127, and objects will be reused between this range. Will create a new Long object:

 public static Integer valueOf(int i) {<!-- -->
        if (i >= IntegerCache. low & amp; & amp; i <= IntegerCache. high)
            return IntegerCache.cache[i + (-IntegerCache.low)];
        return new Integer(i);
    }
  • 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 caches TRUE and FALSE

String pool

  1. String str1 = new String(“This is a string”);
  2. String str2 = “This is a string”;
  • Defined by the keyword new:
    • First look in the string constant pool
      • If it exists, no additional space will be opened up to ensure that there is only one “This is a string” in the character constant area to save space. Then open up a space in the heap area to store the new String object, and open up a space in the stack area to store the variable name str1, and str1 points to the new String object in the heap area
      • If it does not exist, open up a memory space in the string constant pool to store “This is a string”
  • direct definition
    • Find whether there is a “This is a string” constant in the string constant area
      • If it does not exist, open up a memory space in the string constant area to store “This is a string”
      • If it exists, no additional space will be opened, and space will be opened in the stack area to store the variable name str2, and str2 points to the memory address of the string constant pool “This is a string”

6.3.3 DIY

QPS: Queries Per Second, which means “query rate per second”, is the number of queries that a server can respond to per second, and is a measure of how much traffic a specific query server handles within a specified time.

Assumption: For an online shopping mall application, the QPS reaches thousands. 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 time of creating and closing the connection, but also realizes the reuse of the connection, so as not to destroy the database

package com.lv.juc;

import com.mysql.jdbc.Connection;
import com.mysql.jdbc.ExceptionInterceptor;
import com.mysql.jdbc.Extension;
import com.mysql.jdbc.MySQLConnection;
import com.mysql.jdbc.log.Log;
import lombok.extern.slf4j.Slf4j;

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

/**
 * @author XiaofengwanyueLx
 * @date 2023/3/24 9:29
 */
@Slf4j
public class ThreadPoolMain {<!-- -->

    public static void main(String[] args) {<!-- -->
        Pool pool = new Pool(3);
        for(int i = 0; i < 5; i ++ ){<!-- -->
            new Thread(() -> {<!-- -->
                Connection conn = pool.borrow();
                try{<!-- -->
                    Thread. sleep(new Random(). nextInt(1000));
                }catch (Exception e){<!-- -->
                    e.printStackTrace();
                }
                pool. free(conn);
            }).start();
        }
    }

}

@Slf4j
class Pool {<!-- -->
    // 1. Connection pool size
    private final int poolSize;

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

    // 3. Connection status array 0 means idle, 1 means busy
    private AtomicIntegerArray states;

    // 4. Construction method 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 connection){<!-- -->
        for(int i = 0; i < poolSize; i ++ ){<!-- -->
            if (connections[i] == connection){<!-- -->
                states.set(i,0);
                synchronized (this){<!-- -->
                    log.debug("free {}",connection);
                    this. notifyAll();
                }
                break;
            }
        }
    }


}

@Slf4j
class MockConnection implements Connection{<!-- -->
   // Just implement it, don't write, the construction method needs to be written
}

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 more general object pools, you can consider using apache commons pool. For example, for redis connection pools, you can refer to the implementation of connection pools in jedis

6.4 final principle

The principle of final: The bottom layer of final is also implemented through memory barriers, which is the same as volatile.

  • Add a write barrier to the write instruction of the final variable. That is, a write barrier will be added when the class is initialized and assigned.
  • Add a read barrier to the read instruction of the final variable. Load the latest value of the final variable in memory.

1. The principle of setting final variables

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 ptufield instruction, and a write barrier will also be added after this instruction to ensure that other threads will not read its value as 0

2. The principle of obtaining final variables

@Slf4j
public class TestFinal {<!-- -->
    static final int a = 20;

    public static void main(String[] args) {<!-- -->
        System.out.println(a);
    }
}
 <-- read barrier
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: bipush 20
5: invokevirtual #4 // Method java/io/PrintStream.println:(I)V
8: return

It is found that the acquisition of the final variable will also be completed through the getstatic instruction, and a read barrier will also be added before this instruction to ensure that the value read by other threads is the latest value of the final variable in the loaded memory

6.5 Stateless

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 no member variables are called stateless
    • Immutable: There are member variables, stateful, but immutable, thread-safe
    • Stateless: no member variables, stateless, immutable, thread-safe