李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
39.并发编程之不可变类的设计与应用
Leefs
2022-11-11 PM
1322℃
0条
[TOC] ### 一、日期转换的问题 #### 1.1 问题提出 下面的代码在运行时,由于 `SimpleDateFormat` 不是线程安全的 ```java import lombok.extern.slf4j.Slf4j; import java.text.ParseException; import java.text.SimpleDateFormat; /** * @author lilinchao * @date 2022-11-11 * @description 日期转换的问题 **/ @Slf4j(topic = "c.Test07") public class Test07 { 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("2022-11-11")); } catch (ParseException e) { log.error("{}",e); } }).start(); } } } ``` 有很大几率出现 `java.lang.NumberFormatException` 或者出现不正确的日期解析结果,例如: ``` java.lang.NumberFormatException: For input string: "11.11E2" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Long.parseLong(Long.java:589) at java.lang.Long.parseLong(Long.java:631) at java.text.DigitList.getLong(DigitList.java:195) at java.text.DecimalFormat.parse(DecimalFormat.java:2051) at java.text.SimpleDateFormat.subParse(SimpleDateFormat.java:2162) at java.text.SimpleDateFormat.parse(SimpleDateFormat.java:1514) at java.text.DateFormat.parse(DateFormat.java:364) at com.lilinchao.thread.demo08.Test07.lambda$main$0(Test07.java:20) at java.lang.Thread.run(Thread.java:748) java.lang.NumberFormatException: For input string: ".E0" at sun.misc.FloatingDecimal.readJavaFormatString(FloatingDecimal.java:2043) 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:2056) 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 com.lilinchao.thread.demo08.Test07.lambda$main$0(Test07.java:20) at java.lang.Thread.run(Thread.java:748) 09:56:54.318 c.Test07 [Thread-5] - Fri Nov 11 00:00:00 GMT+08:00 2022 09:56:54.318 c.Test07 [Thread-7] - Fri Nov 11 00:00:00 GMT+08:00 2022 ``` #### 1.2 使用同步锁 这样虽能解决问题,但带来的是性能上的损失,并不算很好: ```java import lombok.extern.slf4j.Slf4j; import java.text.SimpleDateFormat; /** * @author lilinchao * @date 2022-11-11 * @description 同步锁 **/ @Slf4j(topic = "c.Test09") public class Test09 { 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.debug("{}", sdf.parse("2022-11-11")); } catch (Exception e) { log.error("{}", e); } } }).start(); } } } ``` **运行结果** ``` 10:05:03.937 c.Test09 [Thread-0] - Fri Nov 11 00:00:00 GMT+08:00 2022 10:05:03.942 c.Test09 [Thread-9] - Fri Nov 11 00:00:00 GMT+08:00 2022 10:05:03.942 c.Test09 [Thread-8] - Fri Nov 11 00:00:00 GMT+08:00 2022 10:05:03.942 c.Test09 [Thread-7] - Fri Nov 11 00:00:00 GMT+08:00 2022 10:05:03.942 c.Test09 [Thread-4] - Fri Nov 11 00:00:00 GMT+08:00 2022 10:05:03.942 c.Test09 [Thread-5] - Fri Nov 11 00:00:00 GMT+08:00 2022 10:05:03.942 c.Test09 [Thread-3] - Fri Nov 11 00:00:00 GMT+08:00 2022 10:05:03.943 c.Test09 [Thread-6] - Fri Nov 11 00:00:00 GMT+08:00 2022 10:05:03.943 c.Test09 [Thread-2] - Fri Nov 11 00:00:00 GMT+08:00 2022 10:05:03.943 c.Test09 [Thread-1] - Fri Nov 11 00:00:00 GMT+08:00 2022 ``` #### 1.3 不可变 如果一个对象在不能够修改其内部状态(属性),那么它就是线程安全的,因为不存在并发修改。 这样的对象在 Java 中有很多,例如在 Java 8 后,提供了一个新的日期格式化类: ```java import lombok.extern.slf4j.Slf4j; import java.time.LocalDate; import java.time.format.DateTimeFormatter; /** * @author lilinchao * @date 2022-11-11 * @description 不可变实现 **/ @Slf4j(topic = "c.Test08") public class Test08 { 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("2022-11-11", LocalDate::from); log.info("{}", date); }).start(); } } } ``` **运行结果** ``` 10:06:04.929 c.Test08 [Thread-0] - 2022-11-11 10:06:04.930 c.Test08 [Thread-3] - 2022-11-11 10:06:04.930 c.Test08 [Thread-1] - 2022-11-11 10:06:04.929 c.Test08 [Thread-7] - 2022-11-11 10:06:04.930 c.Test08 [Thread-9] - 2022-11-11 10:06:04.929 c.Test08 [Thread-6] - 2022-11-11 10:06:04.929 c.Test08 [Thread-2] - 2022-11-11 10:06:04.930 c.Test08 [Thread-5] - 2022-11-11 10:06:04.929 c.Test08 [Thread-4] - 2022-11-11 10:06:04.930 c.Test08 [Thread-8] - 2022-11-11 ``` 可以看 `DateTimeFormatter` 的文档: ``` @implSpec This class is immutable and thread-safe. ``` 不可变对象,实际是另一种避免竞争的方式。 ### 二、不可变设计 另一个大家更为熟悉的 String 类也是不可变的,以它为例,说明一下不可变设计的要素 ```java public final class String implements java.io.Serializable, Comparable
, 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 // ... } ``` #### final 的使用 发现该类、类中所有属性都是 final 的 + 属性用 final 修饰保证了该属性是只读的,不能修改 + 类用 final 修饰保证了该类中的方法不能被覆盖,防止子类无意间破坏不可变性 #### 保护性拷贝 但有同学会说,使用字符串时,也有一些跟修改相关的方法,比如 substring 等,那么下面就看一看这些方法是如何实现的,就以 substring 为例: ```java 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 做出了修改: ```java 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)】。
标签:
并发编程
非特殊说明,本博所有文章均为博主原创。
如若转载,请注明出处:
https://lilinchao.com/archives/2571.html
上一篇
【转载】38.Java双刃剑之Unsafe类详解
下一篇
40.并发编程之享元模式
评论已关闭
栏目分类
随笔
2
Java
326
大数据
229
工具
31
其它
25
GO
47
NLP
4
标签云
持有对象
pytorch
稀疏数组
Spark Core
Spark SQL
LeetCode刷题
DataWarehouse
工具
Hive
GET和POST
机器学习
Scala
Docker
人工智能
查找
MyBatisX
FileBeat
DataX
MyBatis
散列
MySQL
FastDFS
Spark RDD
Eclipse
JVM
设计模式
Flume
递归
哈希表
微服务
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞
评论已关闭