并发编程——6.共享模型之不可变

news2024/9/28 7:22:51

目录

  • 6.共享模型之不可变
    • 6.1.日期转换的问题
      • 6.1.1.问题提出
      • 6.1.2.解决思路——同步锁
      • 6.1.3.解决思路——不可变
    • 6.2.不可变设计
    • 6.3.享元模式
      • 6.3.1.简介
      • 6.3.2.体现
      • 6.3.3.DIY
    • 6.4.final 原理
    • 6.5.无状态

本文笔记整理来自黑马视频https://www.bilibili.com/video/BV16J411h7Rd/?p=197,相关资料可在视频评论区进行获取。

6.共享模型之不可变

6.1.日期转换的问题

6.1.1.问题提出

下面的代码在运行时,由于 SimpleDateFormat 不是线程安全的:

public static void main(String[] args) {
    SimpleDateFormat sdf = new SimpleDateFormat("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();
    }
}

有很大几率出现 java.lang.NumberFormatException 或者出现不正确的日期解析结果,例如:

10:57:56 [Thread-4] c.Solution - {}
java.lang.NumberFormatException: empty String
	at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:1842)
	at sun.misc.FloatingDecimal.parseDouble(FloatingDecimal.java:110)
	at java.lang.Double.parseDouble(Double.java:538)
	at java.text.DigitList.getDouble(DigitList.java:169)
	at java.text.DecimalFormat.parse(DecimalFormat.java:2089)
	at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:1869)
	at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514)
	at java.text.DateFormat.parse(DateFormat.java:364)
	at Solution.lambda$main$0(Test.java:12)
	at java.lang.Thread.run(Thread.java:748)

6.1.2.解决思路——同步锁

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

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

6.1.3.解决思路——不可变

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

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.debug("{}", date);
        }).start();
    }
}

可以看 DateTimeFormatter 的文档:

@implSpec
This class is immutable and thread-safe.

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

6.2.不可变设计

(1)另一个大家更为熟悉的 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
    
    // ...
  
}

(2)final 的使用
发现该类、类中所有属性都是 final 的:

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

(3)保护性拷贝
但有同学会说,使用字符串时,也有一些跟修改相关的方法啊,比如 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);
    }
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

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

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;
        }
    }
    if (offset > value.length - count) {
        throw new StringIndexOutOfBoundsException(offset + count);
    }
    this.value = Arrays.copyOfRange(value, offset, offset+count);
}

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

6.3.享元模式

6.3.1.简介

(1)定义:英文名称为 Flyweight pattern,当需要重用数量有限的同一类对象时。

wikipedia: A flyweight is an object that minimizes memory usage by sharing as much data as possible with other similar objects.

(2)出自:“Gang of Four” design patterns

(3)归类:Structual patterns

6.3.2.体现

(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);
}

注意:

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

(2)String 串池

(3)BigDecimal BigInteger

6.3.3.DIY

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

@Slf4j(topic = "c.Pool")
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 < poolSize; i++) {
            connections[i] = new MockConnection("连接" + (i+1));
        }
    }
    // 5. 借连接
    public Connection borrow() {
    
        while(true) {
            for (int i = 0; i < poolSize; i++) {
                // 获取空闲连接
                if(states.get(i) == 0) {
                    if (states.compareAndSet(i, 0, 1)) {
                        log.debug("borrow {}", connections[i]);
                        return connections[i];
                    }
                }
            }
            // 如果没有空闲连接,当前线程进入等待
            synchronized (this) {
                try {
                    log.debug("wait...");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    // 6. 归还连接
    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 {
 // 实现略
}

使用连接池:

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();
}

以上实现没有考虑:

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

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

6.4.final 原理

理解了 volatile 原理,再对比 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 的情况。

6.5.无状态

在 web 阶段学习时,设计 Servlet 时为了保证其线程安全,都会有这样的建议,不要为 Servlet 设置成员变量,这种没有任何成员变量的类是线程安全的。

因为成员变量保存的数据也可以称为状态信息,因此没有成员变量就称之为【无状态】。

在这里插入图片描述

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

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

相关文章

Unity脚本(二)

视频教程&#xff1a;https://www.bilibili.com/video/BV12s411g7gU/?share_sourcecopy_web Transform 对象的位置、旋转和缩放 场景中的每个对象都有一个Transform&#xff0c;用于存储和操作对象的位置、旋转和缩放。 每个Transform都可以有一个父级&#xff0c;能够分…

C语言强制类型转换

强制类型转换是把变量从一种类型转换为另一种数据类型。例如&#xff0c;如果您想存储一个 long 类型的值到一个简单的整型中&#xff0c;您需要把 long 类型强制转换为 int 类型。您可以使用强制类型转换运算符来把值显式地从一种类型转换为另一种类型&#xff0c;如下所示&am…

gitlab-ci.yml关键字(五)tags 、only 、when

tags 使用Tags用于选择Runner的标签列表 我们在创建Runner 时可以给该Runner打上特定的标签&#xff0c;那后续流水线中的job如果需要使用特定标签的Runner执行时&#xff0c;就需要使用tags来标记 比如这里有两个标签的Runner 也可以对当前的runner进行一些配置上的设置 …

2022总结:我是怎样从一个混子到如今小有所成

前言 &#x1f340;作者简介&#xff1a;被吉师散养、喜欢前端、学过后端、练过CTF、玩过DOS、不喜欢java的不知名学生。 &#x1f341;个人主页&#xff1a;红中 &#x1f342;抽根烟&#xff0c;吹个牛b(不是 入门 如果硬要问我是在什么时候入门的&#xff0c;那就要说到高一…

MySQL添加用户及用户权限管理

目录 1、用户 <1> 用户信息 <2> 创建用户 <3> 删除用户 <4> 修改用户密码 2、用户权限管理 <1> 查看用户权限 <2> 给用户授权 <3> 回收权限 1、用户 <1> 用户信息 MySQL中的用户&#xff0c;都存储在系统数据库mysq…

机器翻译与数据集

机器翻译指的是将文本序列从一种语言自动翻译成另一种语言。 使用单词级词元化时的词表大小&#xff0c;将明显大于使用字符级词元化时的词表大小。为了缓解这一问题&#xff0c;我们可以将低频词元视为相同的未知词元。 通过截断和填充文本序列&#xff0c;可以保证所有的文…

【LeetCode每日一题】——154.寻找旋转排序数组中的最小值 II

文章目录一【题目类别】二【题目难度】三【题目编号】四【题目描述】五【题目示例】六【解题思路】七【题目提示】八【题目进阶】九【时间频度】十【代码实现】十一【提交结果】一【题目类别】 二分查找 二【题目难度】 困难 三【题目编号】 154.寻找旋转排序数组中的最小…

windows下OpenCV安装教程以及vs2019配置opencv教程

文章目录一. OpenCV下载二. OpenCV安装及配置三. VS2019项目配置OpenCV一. OpenCV下载 官网地址&#xff1a;Home - OpenCV 下载地址&#xff1a;OpenCV download | SourceForge.net 二. OpenCV安装及配置 双击下载好的安装包进行安装 安装过程实际上是一个解压过程 选择…

java开发机动车考试驾照考试-科一科四考试在线题库系统

简介 本系统主要是进行科一科四考试和练习的网上考试系统&#xff0c;分为A1B1、A2B2、C1C2的科一科四考试系统&#xff0c;当学员点击开始考试&#xff0c;系统将自动生成随机题目100道&#xff08;选择题80道&#xff0c;判断题20道&#xff09;的试卷&#xff0c;考试时间4…

【数据结构】LeetCode移除元素、删除排序数组中的重复项、合并两个有序数组

目录 一、移除元素 1、题目说明 2、题目解析 二、删除排序数组中的重复项 1、题目说明 2、题目解析 三、合并两个有序数组 1、题目说明 2、题目解析 一、移除元素 1、题目说明 题目链接&#xff1a;移除元素 给你一个数组nums和一个值val&#xff0c;你需要原地移除所有数值等…

php宝塔搭建部署实战彩纸屋在线少儿编程系统源码

大家好啊&#xff0c;我是测评君&#xff0c;欢迎来到web测评。 本期给大家带来一套php开发的彩纸屋在线少儿编程系统源码&#xff0c;感兴趣的朋友可以自行下载学习。 技术架构 PHP7.2 nginx mysql5.7 JS CSS HTMLcnetos7以上 宝塔面板 文字搭建教程 下载源码&#x…

第一个完整的CMake工程

第一个完整的CMake工程一、概述二、准备工作2.1 创建工程2.2 创建源码目录三、换个地方保存目标二进制文件3.1 add_subdirectory 指令说明3.2 重设目标二进制生成目录四、如何安装4.1 目标文件的安装4.2 普通文件的安装4.3 非目标文件的可执行程序安装(比如脚本之类)&#xff1…

git 源码下载安装最新版本

问题 用yum install git 下载后查看版本 git --version 显示的版本太低&#xff0c;自己去github下载比较新的版本 解决 https://github.com/git/git/tags 查看最新的版本&#xff0c;并且复制tar.gz下载链接 wget 你复制的链接 我的例子&#xff1a;wget https://github.…

DevOps:开发运维全流程

目录 &#x1f9e1;什么是DevOps&#xff1f; &#x1f9e1;什么是CI/CD&#xff1f; &#x1f49f;这里是CS大白话专场&#xff0c;让枯燥的学习变得有趣&#xff01; &#x1f49f;没有对象不要怕&#xff0c;我们new一个出来&#xff0c;每天对ta说不尽情话&#xff01; &…

第一章 计算机网络体系结构

目录(1) 概念与功能(2) 组成与分类(3) 标准化工作与组织(4) 性能指标(5) 分层结构、协议、接口、服务(6) 7 层 OSI 参考模型(7) TCP/IP 模型(8) 五层参考模型(1) 概念与功能 1、概念 计算机网络&#xff1a;是一个将分散的、具有独立功能的计算机系统&#xff0c;通过通信设备…

RHI_Shader Compare(HLSL、GLSL、MSL 、WGSL、OpenCL) Debug All

为了后续平台能够更好的封装跨平台Shader&#xff0c;本部分主要来看一下各平台Shader的语法特点、编译方式以及Debug方式等对比。 对于各类Shader语言还是先来看一下官方的文档&#xff0c;里边有具体的阐述&#xff0c;也是比较权威的文档&#xff1a; HLSL官方文档GLSL官…

sftp的基本使用

写在前面 在工作和学习中我们经常需要在linux和本地之间上传和下载文件&#xff0c;本文就一起来看下。 1&#xff1a;连接ftp 在window执行命令sftp 用户名IP,这里的用户名是linux服务器的用户名&#xff0c;IP是linux服务器的IP&#xff0c;如下&#xff1a; [c:\~]$ sft…

mysql统计查询和一行转多行(列转行)--- help_topic

mysql.help_topic help_topic本身是Mysql一个帮助解释注释表&#xff0c;用于解释Mysql各种专有名词&#xff0c;由于这张表数据ID是从0顺序增加的&#xff0c;方便我们用于计数&#xff0c;但是8.0.17版本的只有656条数据&#xff0c;超过这个数字&#xff0c;我们就需要己自定…

【王道操作系统】1.1.1 操作系统的概念、功能和目标(系统资源的管理者、提供接口、作为扩充机器、虚拟机)

操作系统的概念、功能、目标(系统资源的管理者、提供接口、作为扩充机器) 文章目录操作系统的概念、功能、目标(系统资源的管理者、提供接口、作为扩充机器)1.常见的操作系统举例2.操作系统的层次结构3.操作系统的概念4.操作系统的功能和目标4.1 作为计算机系统资源的管理者4.2…

LeetCode题解 二叉树(七):222 完全二叉树的节点个数;110 平衡二叉树;257 二叉树的所有路径

前言 阳过之后&#xff0c;已经有一周多没有接触过一道题目了 从今日开始恢复每日一小时的刷题日常 二叉树 222 完全二叉树的节点个数 medium 无论是深度遍历&#xff08;前中后都好&#xff09;还是层序遍历&#xff0c;都可以用于求解这道题&#xff0c;只需要使用一个额…