共享模型之不可变

news2024/11/14 14:43:01

1.日期转换的问题

1>.代码示例

@Slf4j
public class TestDateFormatDemo1 {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            //多个线程调用日期格式化对象的方法
            new Thread(() -> {
                try {
                    log.info("{}", sdf.parse("1951-04-21"));
                } catch (Exception e) {
                    log.error("{}", e);
                }
            }).start();
        }
    }
}

在这里插入图片描述

//由于目前使用的日期格式化对象的parse()方法并不是线程安全的,因此多线程环境下使用有很大几率出现java.lang.NumberFormatException或者出现不正确的日期解析结果异常;

2>.解决方案: 同步锁

@Slf4j
public class TestDateFormatDemo1 {
    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 {
                        log.info("{}", sdf.parse("1951-04-21"));
                    } catch (Exception e) {
                        log.error("{}", e);
                    }
                }
            }).start();
        }
    }
}

在这里插入图片描述

这样虽能解决问题,但带来的是性能上的损失,并不算很好!

3>.解决方案: 不可变

如果一个对象不能够修改其内部状态(属性),那么它就是线程安全的,因为不存在并发修改!这样的对象在Java中有很多,例如在Java 8后,提供了一个新的日期格式化类;

@Slf4j
public class TestDateFormatDemo1 {
    public static void main(String[] args) {
        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.info("{}", date);
            }).start();
        }
    }
}

在这里插入图片描述

不可变对象,实际是另一种避免竞争的方式!

2.不可变设计–String

1>.大家熟悉的String类也是不可变的,以它为例,说明一下不可变设计的要素;

public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    
    //使用final修饰,只能在构造方法中赋值!!!
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0
 
    //......
}

2.1.final的使用

从源码中得知,String类、类中属性是用final修饰的:

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

***注意:使用final修饰对象,只能保证对象的引用(内存地址)不被改变,但是无法保证对象的内容不被改变!

2.2.保护性拷贝

使用字符串时,也有一些跟修改相关的方法,比如substring等,那么下面就看一看这些方法是如何实现的?就以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);
        }
        //内部是调用String的构造方法创建了一个新字符串,再进入这个构造看看,是否对"final char[] value"做出了修改;
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

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;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
            throw new StringIndexOutOfBoundsException(offset + count);
        }
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

发现并没有修改原始的"char[] value",而是在构造新字符串对象时,生成新的"char[] value",然后对内容进行复制.这种通过创建副本对象来避免共享的手段称之为"保护性拷贝(defensive copy)"!

3.设计模式之享元模式

3.1.简介

1>.英文名称: Flyweight pattern.当需要重用数量有限的同一类对象时,为了避免频繁创建同一类对象,可以使用享元模式;

3.2.体现

3.2.1.包装类

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

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

 return new Long(l);
}

private static class LongCache {
   private LongCache(){}
        static final Long cache[] = new Long[-(-128) + 127 + 1];
        static {
            //缓存256个Long对象,之后使用的时候直接从缓存中取即可,避免对象的重复创建
            for(int i = 0; i < cache.length; i++)
                cache[i] = new Long(i - 128);
        }
}

  //...
}

***注意:

①.Byte,Short,Long缓存的范围都是:-128~127;
②.Character缓存的范围是:0~127;
③.Integer的默认范围是:-128~127;

  • –1).最小值不能变;
  • –2).最大值可以通过调整虚拟机参数"-Djava.lang.Integer.IntegerCache.high"来改变;

④.Boolean缓存了TRUE和FALSE;

除了包装类,还有String类以及BigDecimal,BigInteger类等都是不可变类,体现了享元模式,也是线程安全的!

3.3.基于享元模式自定义简单的数据库连接池

1>.需求:

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

2>.代码实现:

public class TestPool {
    public static void main(String[] args) {
        //创建连接池对象
        Pool pool = new Pool(3);
        //模拟多个线程操作连接池
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                //获取连接
                Connection connection = pool.getConnection();

                //阻塞,模拟业务处理
                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //归还连接
                    pool.freeConnection(connection);
                }
            },"线程"+(i+1)).start();
        }
    }
}

//基于享元模式自定义数据库连接池
@Slf4j
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[this.poolSize];
        this.states = new AtomicIntegerArray(new int[this.poolSize]);

        //循环创建connection对象,放入connection数组中
        for (int i = 0; i < this.poolSize; i++) {
            //假设这些连接对象都是正常可用的
            this.connections[i] = new MockConnection("连接"+(i+1));
        }
    }

    //5.获取连接对象
    public Connection getConnection() {
        while (true) {
            for (int i = 0; i < this.poolSize; i++) {
                //获取空闲连接对象
                if (this.states.get(i) == 0) {
                    //由于该连接对象并没有被具体某个线程持有,也就是说该连接对象可能会被多个线争抢
                    //因此需要使用CAS机制修改该连接对象的状态
                    if (this.states.compareAndSet(i, 0, 1)) {
                        log.info("获取连接:{}", this.connections[i]);
                        //修改连接对象的状态成功的线程才能获取connections数组中对应下标的connection连接对象
                        return this.connections[i];
                    }
                }
            }

            //如果没有空闲连接,线程等待
            //为什么不使用CAS机制让线程一直循环运行着,而是等待?
            //CAS操作适合于短时间运行的代码片段(即对锁资源的占用时间短),让线程可以在短时间内不停尝试获取锁(连接对象),但是目前场景是线程拿到锁(连接对象)之后要操作其他业务,即线程占有锁(连接对象)的时间较长,如果使用CAS机制,那么那些没有获取到锁(连接对象)的线程一直循环不停的运行,对系统资源消耗是非常大的,有可能将系统资源耗尽,最好的方式就是让这些没有获取到锁(连接对象)的线程暂时等待,释放系统资源;
            synchronized (this) {
                try {
                    log.info("{} wait...", Thread.currentThread().getName());
                    this.wait();  //这里可以优化,参考之前的"保护性暂停带超时版"
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    //6.归还连接对象
    public void freeConnection(Connection connection) {
        for (int i = 0; i < this.connections.length; i++) {
            if (this.connections[i] == connection) {
                //由于该连接对象已经被某个具体线程持有,也就是说不存在多个线程同时使用一个连接对象的情况
                //因此这里只需要使用普通的方法修改对应的连接对象的状态即可!!!
                this.states.set(i, 0);

                //已有空闲连接对象,唤醒等待中的线程
                synchronized (this) {
                    log.info("归还连接:{}", connection);
                    this.notifyAll();
                }

                break;
            }
        }
    }
}

//连接对象(由于是测试环境,因此又自定义了一个connection对象,实现connection接口)
class MockConnection implements Connection {

    private String name;

    public MockConnection(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "MockConnection{" +
            "name='" + name + '\'' +
            '}';
    }

   //省略connection接口中的方法实现
}

在这里插入图片描述
***注意: 以上代码只是为了方便理解享元模式,相较于成熟的数据库连接池,有以下方面没有考虑到,切勿用于生产环境!

①.连接的动态增长与收缩
②.连接保活(可用性检测)
③.等待超时处理
④.分布式 hash

对于关系型数据库,有比较成熟的连接池实现,例如c3p0,drui等,对于更通用的对象池,可以考虑使用apache commons pool,例如redis连接池可以参考jedis中关于连接池的实现;

4.final原理

4.1.设置final变量的原理

public class TestFinal {
   final int a = 20;
}

字节码:

0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: bipush 20
7: putfield        #2 // Field a:I
 <-- 写屏障
10: return

final变量的赋值也会通过putfield指令来完成,同样在这条指令之后也会加入写屏障,保证在其它线程读到它的值时不会出现为0的情况;

4.2.获取final变量的原理

在这里插入图片描述
在这里插入图片描述
分析:

如果获取的是final修饰的变量,那么jvm底层使用的是"BIPUSH/LDC"指令,他并没有到final变量所属类中读取变量,而是把final变量的值复制一份到使用类的栈中,整个过程不涉及到共享操作;如果是普通变量(如static变量),那么jvm底层使用的是"GETSTATIC指令",要从变量所属类中读取对应的变量值,需要用到共享内存(/从堆中读取数据),其性能要低于直接使用栈内存;

***注意:

①.如果final变量的值较小,直接复制一份到使用类的栈内存中(BIPUSH);如果final变量的值较大,也会复制一份到使用类的常量池中(LDC);性能较高;
②.如果是普通变量,就相当于在共享内存(堆)中读取(GETSTATIC)变量的值;性能较低;

5.不可变的特例–无状态

在web阶段学习时,设计Servlet时为了保证其线程安全都会有这样的建议: 不要为Servlet设置成员变量,这种没有任何成员变量的类是线程安全的.因为成员变量保存的数据也可以称为状态信息,因此没有成员变量就称之为"无状态";

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

kafka监控工具安装和使用

1. KafkaOffsetMonitor 该监控是基于一个jar包的形式运行&#xff0c;部署较为方便。只有监控功能&#xff0c;使用起来也较为安全(1)消费者组列表 (2)查看topic的历史消费信息. (3)每个topic的所有parition列表(topic,pid,offset,logSize,lag,owner) (4)对consumer消费情况进…

使用Eureka搭建单击模拟到集群模拟

首先讲讲什么是Eureka:1.Eureka是Netflix的子模块&#xff0c;同样也是核心模块之一&#xff0c;Eureka是基于REST的服务&#xff0c;用于定位服务&#xff0c;以实现云端中间件层服务发现和故障转移&#xff0c;服务注册与发现对于微服务来说是非常重要的&#xff0c;有了服务…

vue项目第三天

论坛项目动态路由菜单以及渲染用户登录全局前置拦截器获取用户的菜单以及接口执行过程解析菜单数据&#xff0c;渲染伟动态路由。菜单数据将数据源解析为类似路由配置对象的格式&#xff08;./xxx/xxx 这种格式&#xff09;。下方是路由实例的代码,后面封装了很多方法这里也需要…

RFID服装吊牌材质分类

1、吊牌常见材质 铜版纸&#xff1a;最常用&#xff0c;分单铜纸、双铜纸 白卡纸&#xff1a;厚度较厚 黑卡纸&#xff1a;黑卡纸常用于烫金、烫银工艺 牛皮纸&#xff1a;韧度较高、色彩单一 塑料材料&#xff1a;一般一些比较高档的品牌会选塑料材质&#xff0c;成本比铜…

电商CRM的作用和用途

数据显示&#xff0c;使用电商CRM客户管理系统后&#xff0c;企业销售额提高了87%&#xff0c;客户满意度提高了74%&#xff0c;业务效率提高了73%。要在竞争激烈的电商市场取得成功&#xff0c;与目标受众的有效沟通是有效的方法。下面说说什么是电商CRM系统&#xff1f;电商C…

Docker镜像和容器操作,ლ(´ڡ`ლ)好吃的.

文章目录1.镜像操作1.镜像命令2.情景1&#xff1a;拉取镜像3.情景2&#xff1a;保存导入镜像2.容器操作1.容器命令2.情景1&#xff1a;创建并运行一个容器3.情景2&#xff1a;进入容器&#xff0c;修改文件3.结语halo&#xff0c;大家好&#xff0c;这次我带来的是Docker的一些…

SATA SSD需要NCQ开启吗?

一、故事开篇最近有同学在咨询&#xff0c;SATA SSD是否需要NCQ功能&#xff1f;借此机会&#xff0c;今天我们来聊聊这个比较古老的话题&#xff0c;关于SATA协议的NCQ的故事。首先我们先回顾下SATA与NCQ的历史&#xff1a;2003年&#xff0c;SATA协议1.0问世&#xff0c;传输…

微服务中API网关的作用是什么?

目录 什么是API网关&#xff1f; 为什么要用API网关&#xff1f; API网关架构 API网关是如何实现这些功能的&#xff1f; 协议转换 链式处理 异步请求 什么是API网关&#xff1f; Api网关是微服务的重要组成部分&#xff0c;封装了系统内部的复杂结构&#xff0c;客户端…

蓝牙Mesh学习笔记(一)

Mesh系统结构1 Mesh网络分层1.1 模型层(Model layer)1.2 基础模型层(Foundation Model layer)1.3 接入层(Access layer)1.4 上层传输层(Upper transport layer)1.5 下层传输层(Lower transport layer)1.6 网络层(Network layer)1.7 承载层(Bearer layer)1.8 BLE内核规范(BLE Co…

MySQL性能优化六 事物隔离级别与锁机制

概述 我们的数据库一般都会并发执行多个事务&#xff0c;多个事务可能会并发的对相同的一批数据进行增删改查操作&#xff0c;可能就会导致我们说的脏写、脏读、不可重复读、幻读这些问题。 这些问题的本质都是数据库的多事务并发问题&#xff0c;为了解决多事务并发问题&#…

CUDA内存管理一文理清|参加CUDA线上训练营

CUDA 内存概述 GPU的内存包括&#xff1a; 全局内存&#xff08;global memory&#xff09;常量内存&#xff08;constant memory&#xff09;纹理内存核表面内存&#xff08;texture memory&#xff09;寄存器&#xff08;register&#xff09;局部内存&#xff08;local me…

BI 到底是什么,看看这篇文章怎么说

随着数据价值得到了认可&#xff0c;数据开始成为个人、企业乃至国家的重要战略资产&#xff0c;但数据资产不能直接产生价值&#xff0c;而是需要通过数据分析、数据可视化等数据处理手段将数据转化为信息和知识&#xff0c;才能进行资产的价值化&#xff0c;这时候商业智能BI…

Python骚操作 - 实现把文字写在像素中

前言 嗨嗨&#xff0c;大家好 我是小圆 今天又发来个有意思的 用Python在照片中添加文字~&#xff08;实现把文字写在像素中&#xff09; 那咱就话不多说咯 直接开始展示 实现步骤 想要实现把文字写在像素中&#xff0c;那么我们就需要用到 pillow 这个神器。 众所周知&a…

从零实现高并发WebRTC服务器(六):OpenSSL协议,DTLS协议,RTP协议和SRTP协议

文章目录一、SSL协议二、OpenSSL三、TLS和DTLS四、DTLS的通信的步骤图五、RTP协议和SRTP协议5.1 详解RTP协议5.2 详解RTCP协议5.3 RTP && RTCP的协议的关键技术六、DTLS-SRTP协议一、SSL协议 SSL的全名叫做secure socket layer(安全套接字层)&#xff0c;最开始是由一…

【CSS 布局】 Sticky Footer布局

Sticky footer布局是什么&#xff1f; 我们所见到的大部分网站页面&#xff0c;都会把一个页面分为头部区块、内容区块和页脚区块&#xff0c;当头部区块和内容区块内容较少时&#xff0c;页脚能固定在屏幕的底部&#xff0c;而非随着文档流排布。当页面内容较多时&#xff0c;…

大数据框架之Hadoop:HDFS(三)HDFS客户端操作(开发重点)

3.1 HDFS客户端环境准备 1&#xff0e;根据自己电脑的操作系统拷贝对应的编译后的hadoop jar包到非中文路径&#xff08;例如&#xff1a;D:\javaEnv\hadoop-2.77&#xff09;&#xff0c;如下图所示。 2&#xff0e;配置HADOOP_HOME环境变量&#xff0c;如下图所示。 3&#…

分布式项目-品牌管理(7)

【今日成果】&#xff1a; //啊哈哈哈 &#xff0c; 莫名其妙入选了。 【快速回顾】&#xff1a; &#xff08;1&#xff09;&#xff1a; 虽然提交表单的时候前端做了校验&#xff0c;但是通过PostMAN接口调试&#xff0c;我们发现不规范的数据还是会被存储到数据库中&am…

前端基础知识6

谈谈你对语义化标签的理解语义化标签就是具有语义的标签&#xff0c;它可以清晰地向我们展示它的作用和用途。 清晰的代码结构&#xff1a;在页面没有css的情况下&#xff0c;也能够呈现出清晰的代码内容 有利于SEO: 爬虫依赖标签来确定关键字的权重&#xff0c;因此可以和搜索…

Android 一体机研发之修改系统设置————声音

Android 一体机研发之修改系统设置————屏幕亮度 Android 一体机研发之修改系统设置————声音 Android 一体机研发之修改系统设置————自动锁屏 修改系统设置系列篇章马上开张了&#xff01; 本章将为大家细节讲解声音。 对于声音功能大家都不陌生&#xff0c;在多…

Java虚拟机(JVM)调优思路

title: Java虚拟机&#xff08;JVM&#xff09;调优思路 date: 2022-04-09 00:00:00 tags: JVM性能调优 categories:Java 调什么 内存方面 JVM需要的内存总大小各块内存分配&#xff0c;新生代、老年代、存活区选择合适的垃圾回收算法、控制GC停顿次数和时间解决内存泄露的问…