笨蛋学JUC并发编程-共享模型之不可变

JUC并发编程-共享模型之管程

  • 5.共享模型之不可变
    • 5.1不可变类的使用
      • 日期转换问题
      • 解决-使用synchronized
      • 解决-使用OfPattern
    • 5.2不可变类设计
        • String的substring方法
    • 5.3DIY连接池
    • 5.4final原理
      • 5.4.1设置 final 变量的原理
      • 5.4.2. 获取 final 变量的原理
    • 5.5无状态

5.共享模型之不可变

5.1不可变类的使用

日期转换问题

SimpleDateFormat 不是线程安全的,有很大几率出现 java.lang.NumberFormatException 或者出现不正确的日期解析结果

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        try {
            log.debug("{}", sdf.parse("1951-04-21"));
        } catch (Exception e) {
            log.error("{}", e);
        }
    }).start();
}

解决-使用synchronized

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");

for (int i = 0; i < 50; i++) {
    new Thread(() -> {
        synchronized (sdf) {
            try {
                log.debug("{}", sdf.parse("1951-04-21"));
            } catch (Exception e) {
                log.error("{}", e);
            }
        }
    }).start();
}

解决-使用OfPattern

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");

for (int i = 0; i < 10; i++) {
    new Thread(() -> {
        LocalDate date = dtf.parse("2018-10-01", LocalDate::from);
        log.debug("{}", date);
    }).start();
}

5.2不可变类设计

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];
    /** Cache the hash code for the string */
    private int hash; // Default to 0
    
    // ...
    
}

发现该类中所有属性都是 final 的

  • 属性用 final 修饰保证了该属性是只读的,不能修改
  • 类用 final 修饰保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性
String的substring方法
public String substring(int beginIndex) {
    if (beginIndex < 0) {
        throw new StringIndexOutOfBoundsException(beginIndex);
    }
    int subLen = value.length - beginIndex;
    if (subLen < 0) {
        throw new StringIndexOutOfBoundsException(subLen);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

发现其内部是调用 String 的构造方法创建了一个新字符串,

再进入这个构造看看,是否对 final char[] value 做出了修改:

public String(char value[], int offset, int count) {
    if (offset < 0) {
        throw new StringIndexOutOfBoundsException(offset);
    }
    if (count <= 0) {
        if (count < 0) {
            throw new StringIndexOutOfBoundsException(count);
        }
        if (offset <= value.length) {
            this.value = "".value;
            return;
        }
    }
    if (offset > value.length - count) {
        throw new StringIndexOutOfBoundsException(offset + count);
    }
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}

结果发现也没有,构造新字符串对象时,会生成新的 char[] value,对内容进行复制

这种通过创建副本对象来避免共享的手段称之为**【保护性拷贝(defensive copy)】**

5.3DIY连接池

  • 没有考虑连接的动态增长与收缩
  • 连接保活(可用性检测)
  • 等待超时处理
  • 分布式hash
package com.technologystatck.juc.synchronizeds;

import lombok.extern.slf4j.Slf4j;

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

public class ConnectionPool {
    public static void main(String[] args) {
        Pool pool = new Pool(2);
        for(int i=0;i<5;i++){
            new Thread(()->{
                Connection connection = pool.borrow();
                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                pool.free(connection);

            }).start();
        }

    }
}

@Slf4j(topic = "c.Pool")
class Pool{
    //1.连接池大小
    private final int poolSize;

    //2.连接对象数组
    private Connection[] connections;

    //3.连接状态的数组 0-空闲 1-占用
    private AtomicIntegerArray states;

    //4.构造方法初始化
    Pool(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("连接"+(i+1));
        }
    }

    //5.获取连接
    public Connection borrow(){
        while (true){
            //检查是否有空闲连接
            for(int i=0;i<poolSize;i++){
                //获取空闲连接
                if(states.get(i)==0){
                    //使用比较设置值的方式
                    if (states.compareAndSet(i,0,1)) {
                        log.debug("borrow {}",connections[i]);

                        return connections[i];
                    }
                }
            }
            //若没空闲连接,则让当前线程进入等待
            synchronized (this){
                try {
                    log.debug("wait...");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //6.归还连接
    public void free(Connection connection){
        for(int i=0;i<poolSize;i++){
            if(connections[i] == connection){
                states.set(i,0);
                synchronized (this){
                    log.debug("free {}",connection);
                    this.notifyAll();
                }
                break;
            }
        }

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

    //因实现的Connection方法太多,只显示部分实现的方法
    ... ...
    ... ...
    ... ...
}

5.4final原理

5.4.1设置 final 变量的原理

理解了 volatile 原理,再对比 final 的实现就比较简单了

public class TestFinal {
    final int a = 20; 
}

发现 final 变量的赋值也会通过 putfifield 指令来完成,同样在这条指令之后也会加入写屏障,保证在其它线程读到

它的值时不会出现为 0 的情况

5.4.2. 获取 final 变量的原理

  • 若final的变量修饰的值
    • 值比较小,就在栈内存中
    • 值超过了某类型的最大值,就在常量池中
    • 若值不加final修饰,就在堆中

5.5无状态

  • 不要为Servlet设置成员变量,这种没有任何成员变量的类是线程安全的
  • 因为成员变量保存的数据也可以称为状态信息,因此没有成员变量就称之为【无状态】

你可能感兴趣的:(笨蛋学JUC,java,juc)