目录

SImpleDateFormat格式转换

多线程下安全格式转化

不可变类的设计

保护性拷贝

DIY一个连接池


共享模型中不可变解释对象属性可以更改。

接下来查看一个对象属性可以进行更改产生的线程安全问题

SImpleDateFormat格式转换

多线程下,格式转化使用SimpleDateFormat可能会报错。这是因为线程之间互相影响导致。具体原因查看另一篇博客SimpleDateFormat在多线程下的安全问题-CSDN博客

问题代码如下 

public class test {
    public static void main(String[] args) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(()-&gt;{
                try {
                    Date parse = simpleDateFormat.parse("2003-6-03");
                    System.out.println(parse);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

因为SimpleDateFormat类中calendar属性可以多个线程共享可以多个线程中同时进行改变,因此产生了线程安全问题

线程下安全格式转化

在JDK8之后提供了线程安全的格式转换DateTimeFormatter类。使用方法如下。

public class test {
    public static void main(String[] args) {
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(()-&gt;{
                try {
                    TemporalAccessor parse = dateTimeFormatter.parse("2003-06-03");
                    System.out.println(parse);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

可变类的设计

在Java中,有许多类都采用了不可变设计来避免线程不安全问题。

以String为例

字符串使用char数组来进行存储hash用来存储哈希值,当第一次调用hashCode时会存储hash变量中做缓存

这里需要搞清楚,String中value虽然添加final修饰,代表的是char数组引用可变,不代表char数组中的值不可变。而对于基本类型来说,使用final修饰才代表值不可变。

保护拷贝

以下是String类的构造方法

public String(char value[]) {

    this.value = Arrays.copyOf(value, value.length);

}

对于char数组,如果将参数value引用直接赋值给this.value的话,实际上this.value存储的是一个数地址,而不是存储的值。当外部存在一个char数组引用了String当中的参数数组时,当外部的char数组中的值发生改变时,String对象中的值也跟着改变。因此要拷贝出一样的char数组让value引用拷贝出来的地址

接下来看一个没有采用保护拷贝导致的对象值改变的情况

public class Test {
    public static void main(String[] args) {
        //定义一个char数组
        char[] name = new char[]{'z','s'};
        //另一个数组也引用name的地址
        char[] changeName = name;
        Student student = new Student(name);
        System.out.println(student.name);
        //修改changName的值
        changeName[0]='l';
        System.out.println(student.name);
    }

    static class Student{
        char[] name;

        public Student(char[] name) {
            this.name = name;
        }
    }
}

 输出结果如下

zs
ls

我们修改的是对象外char数组的值,但是对象中的值也随之发生改变,从此我们可以看出保护性拷贝的重要性。 

类似于String类容易频繁创建对象,这时通常会用享元模式来减少对象的创建。对于享元模式不熟悉的小伙伴可以查看我的另一篇文章JDK使用了享元模式的源码解析

DIY一个连接池

连接池作用是可以避免在高并发的情况下反复建立连接浪费系统性能实现连接复用基于享元模式实现的。

连接池代码实现 

public 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(new int[poolSize]);
        for (int i = 0; i < poolSize; i++) {
            connections[i] = new MyConnect("nameIs"+i);
        }
    }

    //获取连接
    public Connection getConnect(){
        while (true){
            for (int i = 0; i < poolSize; i++) {
                //如果该连接空闲
                if (states.get(i)==0){
                    //修改连接状态
                    if (states.compareAndSet(i,0,1)){
                        System.out.println("获取连接"+connections[i]);
                        return connections[i];
                    }
                }
            }
            //如果没有空闲连接
            synchronized (this){
                try {
                    System.out.println("没有空闲连接");
                    //进入阻塞等待其他线程释放来连接
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //释放连接
    public void freeConnect(Connection connection){
        //判断传过来的连接是否是连接池中的。
        for (int i = 0; i < poolSize; i++) {
            if (connections[i]==connection){
                //因为只有一个线程拿到该连接,因此不会发生线程安全问题,直接使用set即可
                states.set(i,0);
                System.out.println("释放连接:"+connection);
                synchronized (this){
                    //唤醒其他阻塞的线程
                    this.notifyAll();
                }
                break;
            }
        }
    }
}

测试 

public class Test{
    public static void main(String[] args) {
        //连接池大小为3
        Pool pool = new Pool(3);
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                Connection connect = pool.getConnect();
                try {
                    //拿到连接的线程进行随机休眠
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //释放连接
                pool.freeConnect(connect);
            }).start();
        }
    }
}

运行结果如下

获取连接MyConnect{name='nameIs0'}

获取连接MyConnect{name='nameIs1'}

没有空闲连接

获取连接MyConnect{name='nameIs2'}

没有空闲连接

释放连接:MyConnect{name='nameIs0'}

获取连接MyConnect{name='nameIs0'}

没有空闲连接

释放连接:MyConnect{name='nameIs2'}

获取连接MyConnect{name='nameIs2'}

释放连接:MyConnect{name='nameIs0'}

释放连接:MyConnect{name='nameIs2'}

释放连接:MyConnect{name='nameIs1'}

 至此就是共享模型之不可变的全部内容

原文地址:https://blog.csdn.net/zmbwcx/article/details/134655204

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_22134.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注