不可变类
例子- 时间转换问题
package com.conrrentcy.atomic;
import java.text.SimpleDateFormat;
public class SimpleDateFormatProblemDemo {
public static void main(String[] args){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for(int i =0; i <10; i++){
new Thread(()->{
try {
System.out.println(sdf.parse("2020-11-24"));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"t"+i).start();
}
}
}
SimpleDateFormat 为啥不是线程安全的 ?
private StringBuffer format(Date date, StringBuffer toAppendTo,
FieldDelegate delegate) {
// Convert input date to time field list
calendar.setTime(date); // not threadsafe
…
}
可以看到,多个线程之间共享变量calendar,并修改calendar。因此在多线程环境下,当多个线程同时使用相同的SimpleDateFormat对象(如static修饰)的话,如调用format方法时,多个线程会同时调用calender.setTime方法,导致time被别的线程修改,因此线程是不安全的。
此外,parse方法也是线程不安全的,parse方法实际调用的是CalenderBuilder的establish来进行解析,其方法中主要步骤不是原子操作。
讨论: 如何解决 ? synchronized , ReentrantLock ,自己手写一个。
解决方案:
1、将SimpleDateFormat定义成局部变量
2、 加一把线程同步锁:synchronized(lock) //竞争 效率低
3、使用ThreadLocal,每个线程都拥有自己的SimpleDateFormat对象副本.
package com.conrrentcy.atomic;
import java.text.SimpleDateFormat;
public class ThreadLocalDataFormatSample {
private static final ThreadLocal<SimpleDateFormat> THREAD_LOCAL = new ThreadLocal<SimpleDateFormat>() {
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd");
}
};
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
SimpleDateFormat sdf = THREAD_LOCAL.get();
System.out.println(sdf.parse("2020-11-24"));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}, "t" + i).start();
}
}
}
以上缺点: 互斥,性能比较低。 有没有无锁,但是有能解决这个问题的办法
使用DateTimeFormatter代替SimpleDateFormat
package com.conrrentcy.atomic;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
public class DateTimeFormatterSample {
public static void main(String[] args){
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for(int i =0; i <10; i++){
new Thread(()->{
try {
TemporalAccessor ta = dtf.parse("2020-11-24");
System.out.println(ta.toString());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
},"t"+i).start();
}
}
}
顺便提一下,使用JDK8全新的日期和时间API :LocalDate、LocalTime、LocalDateTime。Date如果不格式化,打印出的日期可读性极差,可使用LocalDateTime代替。
- LocalDateTime:获取年月日时分秒等于LocalDate+LocalTime
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime); // 2019-11-20T15:04:29.017
LocalDate localDate = localDateTime.toLocalDate();
LocalTime localTime = localDateTime.toLocalTime();
- LocalDate:获取年月日
LocalDate localDate=LocalDate.now();
System.out.println(localDate); // 2019-11-20
- LocalTime:获取时分秒
LocalTime localTime = LocalTime.now();
System.out.println(localTime); // 15:14:17.081
int hour = localTime.getHour();
int minute = localTime.getMinute();
int second = localTime.getSecond();
不可变设计
如果有变化,就再生成一份。
以经典的String 为例,类,属性都是final 的。 属性final 保证是只读的,不可修改。类final保证是类不可被继承,保证方法不被覆盖。
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/**
* The value is used for character storage.
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*
* Additionally, it is marked with {@link Stable} to trust the contents
* of the array. No other facility in JDK provides this functionality (yet).
* {@link Stable} is safe here, because value is never null.
*/
@Stable
private final byte[] value;
/**
* The identifier of the encoding used to encode the bytes in
* {@code value}. The supported values in this implementation are
*
* LATIN1
* UTF16
*
* @implNote This field is trusted by the VM, and is a subject to
* constant folding if String instance is constant. Overwriting this
* field after construction will cause problems.
*/
private final byte coder;
/** Cache the hash code for the string */
private int hash; // Default to 0
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
/**
* If String compaction is disabled, the bytes in {@code value} are
* always encoded in UTF16.
*
* For methods with several possible implementation paths, when String
* compaction is disabled, only one code path is taken.
*
* The instance field value is generally opaque to optimizing JIT
* compilers. Therefore, in performance-sensitive place, an explicit
* check of the static boolean {@code COMPACT_STRINGS} is done first
* before checking the {@code coder} field since the static boolean
* {@code COMPACT_STRINGS} would be constant folded away by an
* optimizing JIT compiler. The idioms for these cases are as follows.
*
* For code such as:
*
* if (coder == LATIN1) { ... }
*
* can be written more optimally as
*
* if (coder() == LATIN1) { ... }
*
* or:
*
* if (COMPACT_STRINGS && coder == LATIN1) { ... }
*
* An optimizing JIT compiler can fold the above conditional as:
*
* COMPACT_STRINGS == true => if (coder == LATIN1) { ... }
* COMPACT_STRINGS == false => if (false) { ... }
*
* @implNote
* The actual value for this field is injected by JVM. The static
* initialization block is used to set the value here to communicate
* that this static final field is not statically foldable, and to
* avoid any possible circular dependency during vm initialization.
*/
static final boolean COMPACT_STRINGS;
static {
COMPACT_STRINGS = true;
}
/**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* <a href="{@docRoot}/../specs/serialization/protocol.html#stream-elements">
* Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
/**
* Initializes a newly created {@code String} object so that it represents
* an empty character sequence. Note that use of this constructor is
* unnecessary since Strings are immutable.
*/
public String() {
this.value = "".value;
this.coder = "".coder;
}
/**
* 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}
*/
@HotSpotIntrinsicCandidate
public String(String original) {
this.value = original.value;
this.coder = original.coder;
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, 0, value.length, null);
}
...
}
理解一下为什么这么写,为什么传入一个String ,直接赋值。 为什么传入一个arr, 就是copy。
保护性拷贝
就是变化后生成新的策略的体现。阅读代码String ->subString
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = length() - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
if (beginIndex == 0) {
return this;
}
return isLatin1() ? StringLatin1.newString(value, beginIndex, subLen)
: StringUTF16.newString(value, beginIndex, subLen);
}
享元模式
结构型模式,重用同样的数据。达到最小化内存使用。
包装类
Long, Integer, Boolean, Byte,Short,Character的valueOf
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);
}
- Byete, Long,Short都是 -128~127
- Character 0-127
- Integer的默认是-128 ~127 ,最小值不能改,最大值可以通过-Djava.lang.Integer.IntegerCache.high来更改。
- Boolean缓存了True 和FALSE。
- String的常量池
- BigDecimal BigInteger
享元模式习题: 写一个DB连接池, 固定 数量的连接池
package com.conrrentcy.atomic;
import java.sql.Array;
import java.sql.Blob;
import java.sql.CallableStatement;
import java.sql.Clob;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.NClob;
import java.sql.PreparedStatement;
import java.sql.SQLClientInfoException;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicIntegerArray;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ConnectionPool {
private static final Logger log = LoggerFactory
.getLogger(ConnectionPool.class);
// 池大小
private final int poolSize;
private volatile Connection[] conections;
private volatile AtomicIntegerArray states;
public ConnectionPool(int size) {
this.poolSize = size;
states = new AtomicIntegerArray(poolSize);
conections = new Connection[poolSize];
for (int i = 0; i < poolSize; i++) {
conections[i] = new MockConnection();
}
}
public Connection fetchConnection() {
while (true) {
for (int i = 0; i < states.length(); i++) {
if (states.get(i) == 0) {
if (states.compareAndSet(i, 0, 1)) {
log.info("found free " + i + ", connection retreived");
return conections[i];
}
}
}
synchronized (this) {
try {
log.info("not found free connection , waiting for free connection");
this.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public void releaseConnection(Connection conn) {
for (int i = 0; i < conections.length; i++) {
if (conections[i] == conn) {
states.compareAndSet(i, 1, 0);
synchronized (this) {
log.info("release connection {} , notify all", i);
this.notifyAll();
}
break;
}
}
}
public static void main(String[] args) {
ConnectionPool pool = new ConnectionPool(2);
for (int i = 0; i < 5; i++) {
new Thread(() -> {
Connection conn = pool.fetchConnection();
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
pool.releaseConnection(conn);
}).start();
}
}
}
class MockConnection implements Connection {
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
// TODO Auto-generated method stub
return false;
}
@Override
public Statement createStatement() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public PreparedStatement prepareStatement(String sql) throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public CallableStatement prepareCall(String sql) throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public String nativeSQL(String sql) throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public void setAutoCommit(boolean autoCommit) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public boolean getAutoCommit() throws SQLException {
// TODO Auto-generated method stub
return false;
}
@Override
public void commit() throws SQLException {
// TODO Auto-generated method stub
}
@Override
public void rollback() throws SQLException {
// TODO Auto-generated method stub
}
@Override
public void close() throws SQLException {
// TODO Auto-generated method stub
}
@Override
public boolean isClosed() throws SQLException {
// TODO Auto-generated method stub
return false;
}
@Override
public DatabaseMetaData getMetaData() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public void setReadOnly(boolean readOnly) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public boolean isReadOnly() throws SQLException {
// TODO Auto-generated method stub
return false;
}
@Override
public void setCatalog(String catalog) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public String getCatalog() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public void setTransactionIsolation(int level) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public int getTransactionIsolation() throws SQLException {
// TODO Auto-generated method stub
return 0;
}
@Override
public SQLWarning getWarnings() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public void clearWarnings() throws SQLException {
// TODO Auto-generated method stub
}
@Override
public Statement createStatement(int resultSetType, int resultSetConcurrency)
throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType,
int resultSetConcurrency) throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<String, Class<?>> getTypeMap() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public void setTypeMap(Map<String, Class<?>> map) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public void setHoldability(int holdability) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public int getHoldability() throws SQLException {
// TODO Auto-generated method stub
return 0;
}
@Override
public Savepoint setSavepoint() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public Savepoint setSavepoint(String name) throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public void rollback(Savepoint savepoint) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public void releaseSavepoint(Savepoint savepoint) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public Statement createStatement(int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public CallableStatement prepareCall(String sql, int resultSetType,
int resultSetConcurrency, int resultSetHoldability)
throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, int[] columnIndexes)
throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public PreparedStatement prepareStatement(String sql, String[] columnNames)
throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public Clob createClob() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public Blob createBlob() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public NClob createNClob() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public SQLXML createSQLXML() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isValid(int timeout) throws SQLException {
// TODO Auto-generated method stub
return false;
}
@Override
public void setClientInfo(String name, String value)
throws SQLClientInfoException {
// TODO Auto-generated method stub
}
@Override
public void setClientInfo(Properties properties)
throws SQLClientInfoException {
// TODO Auto-generated method stub
}
@Override
public String getClientInfo(String name) throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public Properties getClientInfo() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public Array createArrayOf(String typeName, Object[] elements)
throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public Struct createStruct(String typeName, Object[] attributes)
throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public void setSchema(String schema) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public String getSchema() throws SQLException {
// TODO Auto-generated method stub
return null;
}
@Override
public void abort(Executor executor) throws SQLException {
// TODO Auto-generated method stub
}
@Override
public void setNetworkTimeout(Executor executor, int milliseconds)
throws SQLException {
// TODO Auto-generated method stub
}
@Override
public int getNetworkTimeout() throws SQLException {
// TODO Auto-generated method stub
return 0;
}
}
review 大家写的有没有问题。 怎么优化,如果不写wait 可以么? 为什么要加参考LongAdder, 线程离散后,找cell ,这样竞争更少。
以上没有考虑
- 连接数量的扩容和收缩
- 连接的healthy check
- 等待超时
- hash 离散
Final
public class TestFinal {
final int a = 1;
}
字节码为
public class com.conrrentcy.atomic.TestFinal {
final int a;
public com.conrrentcy.atomic.TestFinal();
Code:
0: aload_0
1: invokespecial #12 // Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_1
6: putfield #14 // Field a:I
《------ 写屏障
9: return
}
写屏障保证它的变化一定变化一定被其他 线程读到。
public class TestFinal {
final static int b =10;
final static int c =Short.MAX_VALUE+1;
static int d =100;
static int e =Short.MAX_VALUE+1;
final int f=101;
int g =1000;
}
public class UseFinalToSeeDifference {
TestFinal f = new TestFinal();
public void test(){
System.out.println(TestFinal.b);
System.out.println(TestFinal.c);
System.out.println(TestFinal.d);
System.out.println(TestFinal.e);
System.out.println(f.f);
System.out.println(f.g);
}
}
我们看字节码
public class com.conrrentcy.atomic.UseFinalToSeeDifference {
com.conrrentcy.atomic.TestFinal f;
public com.conrrentcy.atomic.UseFinalToSeeDifference();
Code:
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #12 // class com/conrrentcy/atomic/TestFinal
8: dup
9: invokespecial #14 // Method com/conrrentcy/atomic/TestFinal."<init>":()V
12: putfield #15 // Field f:Lcom/conrrentcy/atomic/TestFinal;
15: return
public void test();
Code:
0: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
3: bipush 10 // 复制到栈
5: invokevirtual #28 // Method java/io/PrintStream.println:(I)V
8: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
11: ldc #34 // int 32768 //常量池去拿
13: invokevirtual #28 // Method java/io/PrintStream.println:(I)V
16: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
19: getstatic #35 // Field com/conrrentcy/atomic/TestFinal.d:I //去堆里拿
22: invokevirtual #28 // Method java/io/PrintStream.println:(I)V
25: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
28: getstatic #39 // Field com/conrrentcy/atomic/TestFinal.e:I //去堆里拿
31: invokevirtual #28 // Method java/io/PrintStream.println:(I)V
34: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
37: aload_0
38: getfield #15 // Field f:Lcom/conrrentcy/atomic/TestFinal;
41: invokevirtual #42 // Method java/lang/Object.getClass:()Ljava/lang/Class;
44: pop
45: bipush 101
47: invokevirtual #28 // Method java/io/PrintStream.println:(I)V
50: getstatic #22 // Field java/lang/System.out:Ljava/io/PrintStream;
53: aload_0
54: getfield #15 // Field f:Lcom/conrrentcy/atomic/TestFinal; // 从堆里拿
57: getfield #46 // Field com/conrrentcy/atomic/TestFinal.g:I // 从堆里拿
60: invokevirtual #28 // Method java/io/PrintStream.println:(I)V
63: return
}
final static 是可以加速运行的。是从常量池去拿而不是是从堆里拿。