栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

不可变类和享元模式

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

不可变类和享元模式

共享模型之不可变类

不可变的变量即使是共享的,也是线程安全的
可变类都不是线程安全的
例如:日期转换问题

package demo1;

import java.text.ParseException;
import java.text.SimpleDateFormat;

public class DateTest {
    public static void main(String[] args) throws Exception {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                try {
                    sdf.parse("2021-10-11");
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }

    }
}

可变类在线程并发的时候会发生线程安全问题
我们可以在上面的例子上加锁来保证线程安全

package demo1;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateTest {
    public static void main(String[] args) throws Exception {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                synchronized (sdf) {
                    try {
                        Date date= sdf.parse("2021-10-11");
                        System.out.println(date);
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }

    }
}

不可变类的使用

但是用锁来解决回对性能有所影响,我们还可以jdk提供的不可变类

代码

package demo1;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;

public class DateTest {
    public static void main(String[] args) throws Exception {
        DateTimeFormatter stf=DateTimeFormatter.ofPattern("yyyy-MM-dd");
        for (int i = 0; i <10 ; i++) {
            new Thread(() -> {
                TemporalAccessor date = stf.parse("2021-10-11");
                System.out.println(date);
            }).start();
        }
        

    }
}
不可变类设计

另一个大家更为熟悉的String类也是不可变的I以它为例,说明- - 下不可变设计的要素

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

  • 属性用final修饰保证了该属性是只读的,不能修改
  • 类用final修饰保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性

保护性拷贝:
复制一个新的
但有同学会说,使用字符串时,也有一些跟修改相关的方法啊,比如substring等,那么下面就看-看这些方法是如何实现的,就以substring为例:


发现其内部是调用String的构造方法创建了-一个新字符串,再进入这个构造看看,是否对final char[] value做出了修改:

结果发现也没有,构造新字符串对象时,会生成新的char[] value,对内容进行复制。这种通过创建副本对象来避免共享的手段称之为保护性拷贝(defensive copy)

享元模式 简介

因为String不可变类每次调用方法的时候都要创建新的对象,这样会使得创建的对象过多,对于不可变类我们通常都会涉及一个设计模式-----享元模式

定义.
英文名称: Flyweight pattern.当需要重用数量有限的同一类对象时。享元模式是想最小化内存,对相同值进行一个共享
比如字符串abc,等下次要用得到的时候就不会创建了,直接使用上次创建的

体现 1、包装类

在JDK中Boolean, Byte, Short, Integer, Long, Character等 包装类提供了valueOf方法,例如Long的valueOf会缓存128~-127之间的Long对象,在这个范围之间会重用对象,大于这个范围,才会新建Long对象:

注意:

Byte, Short, Long缓存的范围都是 -128—127
Character缓存的范围是0~127(大于0)
Integer的默认范围是-128-1271最小值不能变,但最大值可以通过调整虚拟机参数-
Djava. lang . Integer. IntegerCache.high来改变
Boolean缓存了TRUE和FALSE

除了包装类之外还有String串池以及BigDecimal、BigInteger都是不可变类,都是线程安全的(指的是单个方法是线程安全的,但是多个方法的组合并一定是线程安全的)。

DIY

自定义一个数据库连接池

例如:一个线上商城应用,QPS 达到数千,如果每次都重新创建和关闭数据库连接,性能会受到极大影响。这时预先创建好一批连接,放入连接池。- -次请求到达后,从连接池获取连接,使用完毕后再还回连接池,这样既节约了连接的创建和关闭时间,也实现了连接的重用,不至于让庞大的连接数压垮数据库.

package demo1;

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 LianJieChi {
    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(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                pool.free(conn);
            }).start();
        }
    }
}

class Pool{
    //1.连接池大小
    private final int poolSize;
    //2.连接对象的数组
    private Connection[] connections;
    //3.连接状态数组 0标识空闲,1标识繁忙
    private AtomicIntegerArray states;
    //4.构造方法初始化
    public Pool(int poolSize){
        this.poolSize=poolSize;
        this.connections=new Connection[poolSize];
        this.states=new AtomicIntegerArray(new int[poolSize]);
        for (int i = 0; i  

总结

为什么获得连接的时候,用了cas还要将其进入wait等待呢?
cas适合短时间运行的代码片段,用cpu进行操作,但是我们获得数据库连接后是要进行增删改查的操作,这个时间比较长,为了避免cas不断尝试浪费cpu资源,所以将其进入的等待。

final原理 设置final变量的原理

理解了volatile原理,在对比final的是西安就比较简单了


写屏障有两个作用:
1、保证写屏障之前的指令不会被重排序到写屏障后面
2、写屏障之前的赋值操作会被同步到主存,对其他线程可见

获取final变量的原理


final修饰的变量数字比较小在栈内存中,数字比较大在常量池中,不被final修饰的变量是在堆中,在堆中读取的效率没有前两者快

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/318589.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号