共享模型之不可变

文章目录

    • 1. 问题提出
    • 2. 不可变对象的设计
    • 3. 设计模式—享元模式
    • 4. 享元模式案例—自定义连接池
    • 5. final原理

1. 问题提出

我们知道,在并发环境中,引起并发问题的根源是共享变量的存在,而之所以共享变量之所以不安全,是因为多线程可以对它进行写操作。如果一个共享资源它是只读不可写的那么它自然不会引发并发安全问题。例如下面代码:

SimpleDataFormat sdf=new SimpleDataFormat("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();
}

可变类多线程环境下就会出现下面问题会发生下面错误
在这里插入图片描述
下面对其进行改进:

@Slf4j
public class TestJMM {
    public static void main(String[] args) {
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd");
        for(int i=0;i<10;i++){
            new Thread(()->{
            {
                synchronized (sdf){
                    try{
                        System.out.println(sdf.parse("1951-04-21"));;
                    } catch (Exception e) {
                        log.error("{}", e);
                    }
                }
                }
            }).start();
        }
    }
}

我们对其加锁就解决了线程安全问题,但锁会对程序性能会有很大的影响。所以我们考虑使用不变类(DateTimeFormatter)来实现:

@Slf4j
public class TestJMM {
    public static void main(String[] args) {
        DateTimeFormatter sdf=DateTimeFormatter.ofPattern("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();
        }
    }
}

2. 不可变对象的设计

这里拿常用的String类作为模版来设计不可变类

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

    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = -6849794470754667710L;

    /**
     * Class String is special cased within the Serialization Stream Protocol.
     *
     * A String instance is written into an ObjectOutputStream according to
     * <a href="{@docRoot}/../platform/serialization/spec/output.html">
     * 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;
    }

    /**
     * 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}
     */
    public String(String original) {
        this.value = original.value;
        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 = Arrays.copyOf(value, value.length);
    }
}

从源码我们可以看出一些关键点:

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

String再对字符串进行修改时都会创建一个新的字符串对象,而不会对原有的字符串对象进行修改(这就是保护性拷贝)

3. 设计模式—享元模式

在第二部分介绍Stirng类时,在其对共享变量进行保护时利用了保护性拷贝技术。有修改行为发生时会创建新的对象,但这也带来了一个问题,就是会创建太多的对象。因此我们引入了一种设计模式—享元模式。

享元模式是一种结构型设计模式,旨在有效地支持大量细粒度的对象共享。它通过共享对象来减少内存消耗和提高性能。在某些情况下,一个应用程序可能需要创建大量相似的对象,这些对象之间的差异仅在于一些内部状态。如果为每个对象都分配独立的内存,会导致内存占用量急剧增加,降低系统的性能和效率。享元模式通过共享相同的对象实例,来减少对内存的需求。享元模式的核心思想是将对象的状态分为内部状态和外部状态。内部状态是对象的固定部分,可以在多个对象之间共享,而外部状态是对象的变化部分,需要在使用时传递给对象。享元模式将内部状态存储在享元对象中,并将外部状态作为参数传递给方法。

享元模式包含以下几个关键组件:

  1. 享元接口:定义了享元对象的通用接口,通过该接口可以获取内部状态和操作享元对象。
  2. 具体享元:实现了享元接口,并包含内部状态。具体享元对象需要是可共享的,也就是说它们可以在多个上下文中共享。
  3. 享元工厂:负责创建和管理享元对象。它维护一个享元池,用于存储已经创建的享元对象,以便在需要时进行复用。

当客户端需要使用享元对象时,可以通过享元工厂获取对象的实例。如果享元池中已经存在相应的对象实例,则直接返回该实例;如果不存在,则创建一个新的享元对象并添加到享元池中。这样可以确保相同的对象在多个地方共享使用,减少内存消耗。享元模式的优点在于减少内存消耗和提高性能。它适用于存在大量相似对象的场景,特别是当对象的内部状态较少并且可以共享时。然而,享元模式的缺点是需要维护一个享元池,可能会引入额外的复杂性。享元模式通过共享对象实例来减少内存消耗,提高系统性能。它是一种优化内存使用的设计模式,适用于需要大量细粒度对象共享的情况。

具体体现享元模式的思想的时包装类,载JDK中Boolean,Byte,Short,Long,Integer,Charaver等包装类提供了valueOf方法,例如Long的valueOf会环次-128-127之间的Long对象,在这个范围内重用对象,大于这个范围才会使用Long对象:

public static Long valueOf(long l){
	final int offset=128;
	if(l> -128 && l<=127){
		return LongCache.cache[(int)l+offset];
	}
	return new Long(1);
}

注意:Byte,Short,Long的缓存范围都是-128~127,,Character缓存范围时0~-127,Integer的缓存范围时-128~127,最小值不能变,但最大值可以通过调整虚拟机参数-Ojava.lang.Integer.IntegerCache.high来改变,Boolean缓存了TRUE和FALSE

4. 享元模式案例—自定义连接池

  • 分析:

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

public class TestJMM {
    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) {
                    throw new RuntimeException(e);
                }
                pool.free(connection);
            }).start();
        }
    }
}

class Pool{
    //1. 连接池大小
    private final int poolSize;
    //2. 连接对象数组
    private Connection[] connections;
    //3. 连接状态数组
    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 < poolSize; i++) {
            connections[i]=new MockConnection();
        }
    }
    //5. 从连接池中获取连接
    public Connection borrow(){
        while(true){
            for (int i = 0; i < poolSize; i++) {
                //获取空闲连接
                if (states.get(i)==0) {
                    states.compareAndSet(i,0,1);
                    System.out.println("获取连接:"+i);
                    return connections[i];
                }
            }
            //如果没有连接,当前线程进入连接
            synchronized (this){
                try{
                    System.out.println("没有连接进入等待");
                    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){
                    System.out.println("归还连接:"+i);
                    this.notifyAll();
                }
                break;
            }
        }

    }
}

class MockConnection implements Connection{....}

在这里插入图片描述

5. final原理

  • 设置final变量的原理

理解了Volatile原理,再对比final的实现就简单多了。首先给出一段程序:

public class TestFinal{
    final int a=20;
}

我们使用javap查看其字节码

在这里插入图片描述
发现final变量的赋值操作也是通过putfield指令来完成的,同样这条指令之后也会加入写屏障,保证在其它线程读到它的时候不会出现0的情况。

  • 获取final变量的原理

在字节码层面中,在读取final变量时不会直接读取final变量,而是将值比较小的常量的值拷贝到自己的线程的栈中,对值比较大的常量拷贝到自己的常量池中,访问速度会大大提高。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/143801.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

跨境电商邮件营销的策略?外贸营销怎么做?

跨境电商邮件营销怎么做&#xff1f;跨境电商电子邮件营销工具&#xff1f; 随着全球电子商务的快速发展&#xff0c;跨境电商已经成为越来越多企业的选择。在跨境电商领域&#xff0c;邮件营销是一种非常重要的营销手段。蜂邮将探讨跨境电商邮件营销的策略&#xff0c;帮助企…

基于Nvidia Jetson orin nx的 YoloV7 tensorRt加速

准备环境 安装jetPack组件 Jetpack 是 Nvidia为 Jetson系列开发板开发的一款软件开发包&#xff0c;常用的开发工具基本都包括了&#xff0c;并在在安装 Jetpack的时候&#xff0c;会自动的将匹配版本的CUDA、cuDNN、TensorRT等。官方提供套件中默认已经安装&#xff0c;可以通…

【数据结构】超详细一文带小白轻松全面理解 [ 二叉搜索树 ]—— [从零实现&逐过程分析&代码演示简练易懂]

前言 大家好吖&#xff0c;欢迎来到 YY 滴数据结构系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴数据结构专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; 目录 一.二叉搜索树的基本概念…

MATLAB|科研绘图|山脊图

效果图 山脊图介绍 山脊图&#xff08;Ridge Plot&#xff09;&#xff0c;也被称为Joy Plot&#xff0c;是一种用于可视化数据分布的图表&#xff0c;特别是用于显示多个组的分布情况。在这种图表中&#xff0c;每个组的数据分布都通过平滑的密度曲线来表示&#xff0c;这些曲…

基于 Lua 写一个爬虫程序

你想要基于 Lua 写一个爬虫程序来爬取的内容。我可以给你一个基本的框架&#xff0c;但是请注意这只是一个示例&#xff0c;并且你可能需要根据实际情况进行调整。 -- 首先&#xff0c;我们需要引入一些必要的模块 local http require "socket.http" local json r…

Spring Boot项目优雅实现读写分离

文章目录 1. 读写分离简介2. Spring Boot集成MyBatis3. 配置读写分离数据源4. 定义数据源上下文5. 自定义注解和切面6. 在Service层使用注解7. 拓展与分析7.1 多数据源的选择7.2 事务的处理7.3 异常处理7.4 动态数据源切换7.5 Spring Boot版本适配 &#x1f389;欢迎来到架构设…

如何将 .SQL 文件导入到 IDEA自带的MySQL中

首先连接数据库新建数据库右键选择该数据库选择如下&#xff1a;找到对应的sql文件即可

工作常遇,Web自动化测试疑难解答,测试老鸟带你一篇打通...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、自动化测试中隐…

mac项目流程管理 OmniPlan Pro 4 中文最新 for mac

在OmniPlan Pro 4中&#xff0c;用户可以创建详细的项目计划&#xff0c;包括任务、资源、时间表、预算等设置。同时&#xff0c;软件支持任务管理&#xff0c;让用户能够创建、编辑和删除任务&#xff0c;设置任务的优先级、依赖关系、持续时间、起始日期等。对于资源管理&…

记一次 .NET 某券商论坛系统 卡死分析

一&#xff1a;背景 1. 讲故事 前几个月有位朋友找到我&#xff0c;说他们的的web程序没有响应了&#xff0c;而且监控发现线程数特别高&#xff0c;内存也特别大&#xff0c;让我帮忙看一下怎么回事&#xff0c;现在回过头来几经波折&#xff0c;回味价值太浓了。 二&#…

图论17-有向图的强联通分量-Kosaraju算法

文章目录 1 概念2 Kosaraju算法2.1 在图类中设计反图2.2 强连通分量的判断和普通联通分量的区别2.3 代码实现 1 概念 2 Kosaraju算法 对原图的反图进行DFS的后序遍历。 2.1 在图类中设计反图 // 重写图的构造函数public Graph(TreeSet<Integer>[] adj, boolean dire…

又双叒!宏电5G RedCap工业智能网关获得首个基于RedCap终端场景的华为技术认证

近日&#xff0c;宏电Z2 V20 5G RedCap工业智能网关率先通过华为OpenLab全球开放实验室的系列严格验证流程&#xff0c;完成基于华为RedCap终端场景的兼容性测试&#xff0c;首个获得华为Cloud Open Labs授予的HUAWEI COMPATIBLE证书及其相关认证徽标使用权。 宏电5G RedCap工业…

Elasticsearch:检索增强生成 (Retrieval Augmented Generation -RAG)

作者&#xff1a;JOE MCELROY 什么是检索增强生成 (RAG) 以及该技术如何通过提供相关源知识作为上下文来帮助提高 LLMs 生成的响应的质量。 生成式人工智能最近取得了巨大的成功和令人兴奋的成果&#xff0c;其模型可以生成流畅的文本、逼真的图像&#xff0c;甚至视频。 就语…

【移远QuecPython】EC800M物联网开发板的硬件TIM定时器精准延时

【移远QuecPython】EC800M物联网开发板的硬件TIM定时器精准延时 文章目录 导入库定时器初始化延时函数定时中断回调调用附录&#xff1a;列表的赋值类型和py打包列表赋值BUG复现代码改进优化总结 py打包 首先 这个定时器是硬件底层级别的 优先级最高 如果调用 会导致GNSS等线程…

理疗养生服务预约小程序要如何做

不少人面对身体症状疼痛&#xff0c;往往不会选择去医院&#xff0c;而是去理疗养生馆&#xff0c;选择艾灸、拔罐、中药贴敷等方式治疗改善或减轻疼痛。随着人们对中医信赖度增强&#xff0c;理疗养生市场增长迅速。 而在增长的同时&#xff0c;我们也注意到理疗养生馆经营痛…

Java版B/S架构云his医院信息管理系统源码(springboot框架)

一、技术框架 ♦ 前端&#xff1a;AngularNginx ♦ 后台&#xff1a;JavaSpring&#xff0c;SpringBoot&#xff0c;SpringMVC&#xff0c;SpringSecurity&#xff0c;MyBatisPlus&#xff0c;等 ♦ 数据库&#xff1a;MySQL MyCat ♦ 缓存&#xff1a;RedisJ2Cache ♦ 消息队…

工作汇报怎么写?建议收藏

整体思路与模块&#xff1a; 背景/事件 成果展示 推动落实的方法论 收获与成长 存在的不足及改进措施 下一步工作安排 支持&#xff08;选&#xff09; 一、背景/事件 对于区分“功能性总结”和“应付性总结”&#xff0c;在背景/事件方面有一个关键点 是报告是否具有…

Linux 系统目录结构

Linux 系统目录结构 登录系统后&#xff0c;在当前命令窗口下输入命令&#xff1a; ls / 你会看到如下图所示: 以下是对这些目录的解释&#xff1a; /bin&#xff1a; bin 是 Binaries (二进制文件) 的缩写, 这个目录存放着最经常使用的命令。 /boot&#xff1a; 这里存放…

第二章 (导数与微分)

导数简介 路程与时间关系函数 就是 速度与时间关系函数 的 原函数。 路程与时间关系函数 求导 &#xff08;或者叫导函数&#xff09; —————求导—————> 就是 vt关系的导数 求导得到》导函数 导函数积分 得到 原函数 你一开始速度为0&#xff0c;然后速度不断…

Perl的LWP::UserAgent库爬虫程序怎么写

Perl的LWP::UserAgent库是一个用于发送HTTP请求的Perl模块。它可以用于编写Web爬虫、测试Web应用程序、自动化Web操作等。以下是一个简单的使用LWP::UserAgent库发送HTTP GET请求的Perl脚本的例子&#xff1a; #!/usr/bin/perluse strict; use warnings; use LWP::UserAgent;# …