天天看点

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'}
           

当然上述代码只是为了说明问题,作为连接池来说还有很多不完善的地方。