共享模型之不可变

不可变类

例子- 时间转换问题

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 是可以加速运行的。是从常量池去拿而不是是从堆里拿。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 本章内容 不可变类的使用 不可变类设计 无状态类设计 1、日期转换的问题 问题提出 下面的代码在运行时,由于 Si...
    zhemehao819阅读 2,463评论 0 0
  • 第十一章 常用类的概述和使用 11.1 常用包 11.1.1 包名和名称 java.lang 包,由虚拟机自动导入...
    青山常客阅读 1,886评论 0 0
  • 来自拉钩教育-JAVA就业集训营 1.可变字符串 2. 日期相关类 -----------------------...
    Yuanc丶阅读 3,308评论 0 0
  • 来自拉钩教育-JAVA就业集训营 可变字符串类和日期相关类 可变字符串类(重点) 基本概念 由于String类描述...
    阿木木笔记阅读 1,805评论 0 0
  • Java 常用类 记录一些Java 学习使用, 经常使用类进行总结.. 这个文章感觉还是很有必要的 后面会常总结扩...
    Java慈祥阅读 3,034评论 0 1

友情链接更多精彩内容