天天看點

Java多線程——不可變類的設計不可變定義不可變類的使用不可變類的設計 

不可變定義

某些對象是不可以改變的,在沒有線程修改其中變量,即使對象是共享的,其也是線程安全的。

可變類舉例

package com.leolee.multithreadProgramming.juc.immutable;

import lombok.extern.slf4j.Slf4j;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * @ClassName Test
 * @Description: TODO
 * @Author LeoLee
 * @Date 2021/1/13
 * @Version V1.0
 **/
@Slf4j
public class Test {

    public void mutableTest() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                try {
                    log.info("{}", sdf.parse("2020-02-12"));
                } catch (ParseException e) {
                    e.printStackTrace();
                    log.error("{}", e);
                }
            }).start();
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        test.mutableTest();
    }
}


執行結果:
Exception in thread "Thread-5" Exception in thread "Thread-4" Exception in thread "Thread-1" java.lang.NumberFormatException: For input string: ""
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65)
	at java.lang.Long.parseLong(Long.java:601)
	at java.lang.Long.parseLong(Long.java:631)
	at java.text.DigitList.getLong(DigitList.java:195)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2051)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.leolee.multithreadProgramming.juc.NotVariable.lambda$variableTest$0(NotVariable.java:23)
	at java.lang.Thread.run(Thread.java:745)
java.lang.NumberFormatException: multiple points
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1914)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.leolee.multithreadProgramming.juc.NotVariable.lambda$variableTest$0(NotVariable.java:23)
	at java.lang.Thread.run(Thread.java:745)
java.lang.NumberFormatException: multiple points
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1914)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2056)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at com.leolee.multithreadProgramming.juc.NotVariable.lambda$variableTest$0(NotVariable.java:23)
	at java.lang.Thread.run(Thread.java:745)
10:17:07.926 [Thread-8] INFO com.leolee.multithreadProgramming.juc.NotVariable - Wed Feb 12 00:00:00 CST 2020
10:17:07.926 [Thread-7] INFO com.leolee.multithreadProgramming.juc.NotVariable - Wed Feb 12 00:00:00 CST 2020
10:17:07.926 [Thread-9] INFO com.leolee.multithreadProgramming.juc.NotVariable - Wed Feb 12 00:00:00 CST 2020
10:17:07.926 [Thread-2] INFO com.leolee.multithreadProgramming.juc.NotVariable - Wed Feb 12 00:00:00 CST 2020
10:17:07.926 [Thread-3] INFO com.leolee.multithreadProgramming.juc.NotVariable - Sat Feb 12 00:00:00 CST 2022
10:17:07.926 [Thread-0] INFO com.leolee.multithreadProgramming.juc.NotVariable - Wed Feb 12 00:00:00 CST 2020
10:17:07.926 [Thread-6] INFO com.leolee.multithreadProgramming.juc.NotVariable - Wed Feb 12 00:00:00 CST 2020
           

這是一個典型線程安全問題。通常情況下處理這種問題第一反應就是加鎖,使用synchronized或者ReentrantLock

/*
     * 功能描述: <br>
     * 〈可變類的舉例〉可變類的線程安全需要用鎖來保護
     * @Param: []
     * @Return: void
     * @Author: LeoLee
     * @Date: 2021/1/13 10:30
     */
    public void mutableTest() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                synchronized (sdf) {
                    try {
                        log.info("{}", sdf.parse("2020-02-12"));
                    } catch (ParseException e) {
                        e.printStackTrace();
                        log.error("{}", e);
                    }
                }
            }).start();
        }
    }
           

雖然使用鎖解決了線程安全的問題,但是性能下降也是很大的。

不可變類的使用

對于時間格式化來說,JDK8引入了不可變類DateTimeFormatter

/*
     * 功能描述: <br>
     * 〈不可變類DateTimeFormatter〉
     * @Param: []
     * @Return: void
     * @Author: LeoLee
     * @Date: 2021/1/13 10:31
     */
    public void dateTimeFormatterTest() {
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                TemporalAccessor temporalAccessor = dtf.parse("2020-02-12");
                log.info("{}", temporalAccessor.toString());
            }).start();
        }
    }

執行結果:
10:31:27.261 [Thread-0] INFO com.leolee.multithreadProgramming.juc.immutable.Test - Wed Feb 12 00:00:00 CST 2020
10:31:27.282 [Thread-9] INFO com.leolee.multithreadProgramming.juc.immutable.Test - Wed Feb 12 00:00:00 CST 2020
10:31:27.282 [Thread-8] INFO com.leolee.multithreadProgramming.juc.immutable.Test - Wed Feb 12 00:00:00 CST 2020
10:31:27.283 [Thread-7] INFO com.leolee.multithreadProgramming.juc.immutable.Test - Wed Feb 12 00:00:00 CST 2020
10:31:27.283 [Thread-6] INFO com.leolee.multithreadProgramming.juc.immutable.Test - Wed Feb 12 00:00:00 CST 2020
10:31:27.283 [Thread-5] INFO com.leolee.multithreadProgramming.juc.immutable.Test - Wed Feb 12 00:00:00 CST 2020
10:31:27.283 [Thread-4] INFO com.leolee.multithreadProgramming.juc.immutable.Test - Wed Feb 12 00:00:00 CST 2020
10:31:27.284 [Thread-3] INFO com.leolee.multithreadProgramming.juc.immutable.Test - Wed Feb 12 00:00:00 CST 2020
10:31:27.284 [Thread-2] INFO com.leolee.multithreadProgramming.juc.immutable.Test - Wed Feb 12 00:00:00 CST 2020
10:31:27.284 [Thread-1] INFO com.leolee.multithreadProgramming.juc.immutable.Test - Wed Feb 12 00:00:00 CST 2020
           

不可變類的設計

另一個熟知的不可變類就是String,以String為例來說明一下不可變類的設計要素。

final修飾

//使用final修飾類,防止子類繼承重寫,破壞不可變性
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    //final保證了字元串内容不可更改,且為私有的
    private final char value[];

    /** Cache the hash code for the string */
    //hashcode也是私有的,并且沒有對外提供set方法
    private int hash; // Default to 0

    //省略其他代碼......
}
           
  • 修飾類,方式子類繼承重寫,破壞不可變性
  • 修飾屬性,保證屬性隻讀,不可修改

保護性拷貝

通過建立副本對象來必變共享的手段成為保護性拷貝。

String類中使用final修飾value數組,僅保證屬性的引用不可修改,但是并不能保證數組内容不可修改。String是怎麼保證的呢?

/**
     * Initializes a newly created {@code String} object so that it represents
     * the same sequence of characters as the argument; in other words, the
     * newly created string is a copy of the argument string. Unless an
     * explicit copy of {@code original} is needed, use of this constructor is
     * unnecessary since Strings are immutable.
     *
     * @param  original
     *         A {@code String}
     */
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

    /**
     * Allocates a new {@code String} so that it represents the sequence of
     * characters currently contained in the character array argument. The
     * contents of the character array are copied; subsequent modification of
     * the character array does not affect the newly created string.
     *
     * @param  value
     *         The initial value of the string
     */
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }
           

從String的構造方法可以看出:

  • 如果是String類型的參數的構造方法,直接使用原始String對象的value和hash
  • 如果是數組類型參數的構造方法,會将源數組拷貝指派給value。防止外部數組改變導緻的該String對象中的數組改變,String類中處處都存在這種設計。

如:

public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        //如果beginIndex不等于0,直接建立了一個新的String對象來傳回,絕不改變原始String的内容
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }
           

拓展

由于通常不可變類都使用了這種保護性拷貝手段,這樣就會在使用過程中建立大量的副本對象,是以這些不可變類通常也使用了享元模式。比如包裝類Long使用了享元模式緩存了部分資料。

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);
        }
    }

    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);
    }
           

典型的就是連接配接池,下面是一個簡易的連接配接池實作

package com.leolee.multithreadProgramming.immutable.pool;

import lombok.extern.slf4j.Slf4j;

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

/**
 * @ClassName CustomizedPool
 * @Description: 享元模式自定義連接配接池(固定大小連接配接池)
 * @Author LeoLee
 * @Date 2021/1/13
 * @Version V1.0
 **/
@Slf4j
public class CustomizedPool {

    //連接配接池大小
    private final int poolSize;

    //連接配接對象數組
    private Connection[] connections;

    //連接配接狀态數組 0-空閑 1-繁忙
    private AtomicIntegerArray states;

    public CustomizedPool(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));
        }
    }

    //使用連接配接
    public Connection getConnection() {
        while (true) {
            for (int i = 0; i < poolSize; i++) {
                if (states.get(i) == 0) {//空閑判斷
                    if (states.compareAndSet(i, 0, 1)) {//CAS改變連接配接狀态
                        log.info("get {}", connections[i]);
                        return connections[i];
                    }
                }
            }
            //沒有空閑連接配接,目前線程進入等待
            synchronized (this) {
                try {
                    log.info("wait...");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //歸還連結
    public void free(Connection connection) {
        for (int i = 0; i < poolSize; i++) {
            if (connections[i] == connection) {
                states.set(i, 0);
                synchronized (this) {
                    log.info("free {}", connection);
                    this.notifyAll();
                }
                break;
            }
        }
    }

}

/* 模拟實作一個connection(假實作) */
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;
    }
}

測試代碼:
    public static void main(String[] args) {
        CustomizedPool customizedPool = new CustomizedPool(3);
        for (int i = 0; i < 7; i++) {
            new Thread(() -> {
                Connection connection = customizedPool.getConnection();
                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                customizedPool.free(connection);
            }).start();
        }
    }

執行結果:
13:30:14.268 [Thread-3] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - wait...
13:30:14.268 [Thread-0] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - get MockConnection{name='connection1'}
13:30:14.268 [Thread-2] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - get MockConnection{name='connection3'}
13:30:14.286 [Thread-6] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - wait...
13:30:14.268 [Thread-1] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - get MockConnection{name='connection2'}
13:30:14.286 [Thread-5] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - wait...
13:30:14.286 [Thread-4] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - wait...
13:30:14.579 [Thread-0] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - free MockConnection{name='connection1'}
13:30:14.579 [Thread-4] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - get MockConnection{name='connection1'}
13:30:14.579 [Thread-5] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - wait...
13:30:14.579 [Thread-6] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - wait...
13:30:14.579 [Thread-3] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - wait...
13:30:14.640 [Thread-2] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - free MockConnection{name='connection3'}
13:30:14.640 [Thread-6] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - wait...
13:30:14.640 [Thread-3] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - get MockConnection{name='connection3'}
13:30:14.640 [Thread-5] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - wait...
13:30:15.255 [Thread-1] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - free MockConnection{name='connection2'}
13:30:15.255 [Thread-5] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - get MockConnection{name='connection2'}
13:30:15.255 [Thread-6] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - wait...
13:30:15.348 [Thread-4] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - free MockConnection{name='connection1'}
13:30:15.348 [Thread-6] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - get MockConnection{name='connection1'}
13:30:15.587 [Thread-3] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - free MockConnection{name='connection3'}
13:30:15.731 [Thread-6] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - free MockConnection{name='connection1'}
13:30:15.999 [Thread-5] INFO com.leolee.multithreadProgramming.immutable.pool.CustomizedPool - free MockConnection{name='connection2'}
           

當然上述代碼隻是為了說明問題,作為連接配接池來說還有很多不完善的地方。