JUC Note-Sharing Model Invariant

1. Design of Invariant Classes

Invariant: An object is an immutable object if it cannot modify its internal state (properties)

Thread-safe, concurrent modification and visibility issues for immutable objects are another way to avoid competition

String classes are also immutable, and all attributes within the class are final

  • The final modification of the class guarantees that the methods in the class cannot be overridden, preventing subclasses from unintentionally destroying immutability

  • No write method (set) ensures that internal properties cannot be modified externally

  • Property modification with final ensures that it is read-only and cannot be modified

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];
        //....
    }
    
  • When you change the String class data, a new string object is constructed, a new char[] value is generated, and a copy object is created to avoid sharing in a way called a protective copy

2. final

principle

public class TestFinal {
	final int a = 20;
}

Byte code:

0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 20		// Place values directly on the stack
7: putfield #2 		// Field a:I
<-- Write barrier
10: return

The final variable is assigned through the putfield directive, after which a write barrier is added to ensure that no zero occurs when other threads read its value

Other threads accessing final-decorated variables make a copy and put it on the stack, which is more efficient

3. Hedonic Design Mode

  • The introduction defines the English name Flyweight pattern, which reuses a limited number of objects of the same type.
    • Structural pattern

The enjoyment mode reflects:

1. Packaging classes such as Boolean, Byte, Short, Integer, Long, Character etc. in JDK provide valueOf methods. For example, Long's valueOf caches Long objects between -128 and 127. Objects are reused between these ranges, beyond which new Long objects are 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);
}
  • The range of Byte, Short, Long caches is -128-127
  • Character cache range is 0-127
  • Boolean caches TRUE and FALSE
  • Integer has a default range of -128~127, and the minimum cannot be changed, but the maximum can be changed by adjusting the virtual machine parameter'-Djava.lang.Integer.IntegerCache.high'

2. String String Pool
3,BigDecimal, BigInteger

4. Implement a simple connection pool

For example: an online store application with thousands of QPS will have a significant performance impact if database connections are re-created and closed each time. A batch of connections are pre-created and put into the connection pool. Once a request arrives, the connection is acquired from the connection pool and returned to the connection pool after it has been used. This saves connection creation and closure time and enables connection reuse without overwhelming the database with a large number of connections.

/**
 * Description: Simple Connection Pool
 *
 * @author guizy
 * @date 2020/12/29 21:21
 */
public class Test2 {
    public static void main(String[] args) {
        /*Use connection pool*/
        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 = "guizy.Pool")
class Pool {
    // 1. Connection pool size
    private final int poolSize;

    // 2. Array of Connected Objects
    private Connection[] connections;

    // 3. Connection state array: 0 for idle, 1 for busy
    private AtomicIntegerArray states;

    // 4. Initialization of construction methods
    public Pool(int poolSize) {
        this.poolSize = poolSize;
        this.connections = new Connection[poolSize];
        this.states = new AtomicIntegerArray(new int[poolSize]);//Use AtomicIntegerArray to keep states thread safe
        for (int i = 0; i < poolSize; i++) {
            connections[i] = new MockConnection("Connect" + (i + 1));
        }
    }

    // 5. Borrow Connections
    public Connection borrow() {
        while (true) {
            for (int i = 0; i < poolSize; i++) {
                // Get idle connections
                if (states.get(i) == 0) {
                    if (states.compareAndSet(i, 0, 1)) {//Use compareAndSet to secure threads
                        log.debug("borrow {}", connections[i]);
                        return connections[i];
                    }
                }
            }
            // If there is no idle connection, the current thread enters the wait, and if this synchronized is not written, other threads will not wait. 
            // Always above while(true), idle, consuming cpu resources
            synchronized (this) {
                try {
                    log.debug("wait...");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    // 6. Return Connections
    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 + '\'' +
                '}';
    }
    
    // Connection implementation slightly
}
22:01:07.000 guizy.Pool [Thread-2] - wait...
22:01:07.000 guizy.Pool [Thread-0] - borrow MockConnection{name='Connection 1'}
22:01:07.005 guizy.Pool [Thread-4] - wait...
22:01:07.000 guizy.Pool [Thread-1] - borrow MockConnection{name='Connection 2'}
22:01:07.006 guizy.Pool [Thread-3] - wait...
22:01:07.099 guizy.Pool [Thread-0] - free MockConnection{name='Connection 1'}
22:01:07.099 guizy.Pool [Thread-2] - wait...
22:01:07.099 guizy.Pool [Thread-3] - borrow MockConnection{name='Connection 1'}
22:01:07.099 guizy.Pool [Thread-4] - wait...
22:01:07.581 guizy.Pool [Thread-3] - free MockConnection{name='Connection 1'}
22:01:07.582 guizy.Pool [Thread-2] - borrow MockConnection{name='Connection 1'}
22:01:07.582 guizy.Pool [Thread-4] - wait...
22:01:07.617 guizy.Pool [Thread-1] - free MockConnection{name='Connection 2'}
22:01:07.618 guizy.Pool [Thread-4] - borrow MockConnection{name='Connection 2'}
22:01:07.955 guizy.Pool [Thread-4] - free MockConnection{name='Connection 2'}
22:01:08.552 guizy.Pool [Thread-2] - free MockConnection{name='Connection 1'}

V. State

Stateless: The data stored by a member variable is also known as status information. Stateless means that there is no member variable

Servlets generally do not set member variables for Servlets to keep their threads safe. Classes that do not have any member variables are thread safe

Reference resources:

Keywords: Java Back-end JUC

Added by daniel_lee_hill on Mon, 07 Feb 2022 19:18:10 +0200