不可變定義
某些對象是不可以改變的,在沒有線程修改其中變量,即使對象是共享的,其也是線程安全的。
可變類舉例
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'}
當然上述代碼隻是為了說明問題,作為連接配接池來說還有很多不完善的地方。