JAVA - 并发之不可变与享元与连接池

image

不可变

日期类的转换问题

image
image

加锁解决

image

日期转换的线程安全类

image

不可变类举例 - String类

image
image
image
image
image

享元模式

image
image
image
BigDecimal也是使用了保护性拷贝
image

不可变类的线程安全问题分析

写个简单的连接池,体会享元模式

public class Test3 {

    public static void main(String[] args) {
        Pool pool =new Pool(2);
        for (int i=0;i<5;i++){
            new Thread(()->{
                Connection conn = pool.borrow();
                try {
                    Thread.sleep(new Random().nextInt(3000));
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }finally {
                    pool.free(conn);
                }
            },"t"+i).start();
        }
    }

}

@Slf4j(topic = "c.test")
class Pool{

    //连接池大小
    private final int poolSize;

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

    //每个连接对象的状态,0-空闲,1-繁忙,此处需要考虑并发,比如从繁忙到空闲,从空闲到繁忙
    private AtomicIntegerArray states;

    public Pool(int poolSize){
        this.poolSize=poolSize;
        this.connections=new Connection[poolSize];
        this.states = new AtomicIntegerArray(poolSize);
        for (int i=0;i<poolSize;i++){
            connections[i]=new MockConnection("连接-"+i);
        }
    }

    public Connection borrow(){
        //要一直循环,如果不是循环,那么会有一种情况,没有获取到连接对象,遍历完后或者唤醒后,方法就结束了,这样调用方又要再调一次。一直循环,就是直到获取到为止。
        while (true){
            //
            for(int i=0;i<poolSize;i++){
                if (states.get(i)==0){
                    //此处就体现了为什么状态要使用原子类,比如线程1和线程2同时获取到某个连接的状态0,相当于两个都获取到连接,因此,此时需要cas操作,有一个成功,有一个失败。
                    if (states.compareAndSet(i,0,1)){
                        log.debug("borrow {}",connections[i]);
                        return connections[i];
                    }
                }
            }
            //获取不到,进入等待。没有下面这段代码也是可行的,但是,可能会有这种情况,获取连接对象是为了连接数据库做增删改查,时间会比较久,不等待的话,就会一直for循环获取连接,就会使CPU空转
            synchronized (this){
                try {
                    log.debug("wait....");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //释放连接对象,此处没有并发操作,因为是由持有该连接对象的线程来释放,没有其他线程来干扰
    public void free(Connection conn){
        for (int i =0;i<poolSize;i++){
            if (connections[i]==conn){
                states.set(i,0);
                synchronized (this){
                    log.debug("free:{}",conn);
                    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 + '\'' +
                '}';
    }
    
    //省略了要实现的方法
    //...
}

final原理

image
如果没有final,即int a= 20,会有线程安全的问题,因为int a = 20 ,有两步操作,第一步a默认为0,第二步设a为20(此处不懂可以去看jvm的笔记),如果没有final,多线程下可能会读取到0
image
final修饰,执行速度快
image
没有final要准备10,然后getstatic,速度慢
image

posted @ 2026-02-06 17:46  dvdhellohaha  阅读(10)  评论(0)    收藏  举报