李林超博客
首页
归档
留言
友链
动态
关于
归档
留言
友链
动态
关于
首页
Java
正文
39.并发编程之不可变类的设计与应用
Leefs
2022-11-11 PM
723℃
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
标签云
Spark RDD
字符串
ClickHouse
Tomcat
MyBatisX
栈
Spark Core
gorm
MyBatis-Plus
Spark Streaming
正则表达式
Livy
高并发
并发线程
SpringCloudAlibaba
线程池
SpringCloud
散列
Spark
Stream流
Beego
稀疏数组
Map
Hive
人工智能
NIO
VUE
持有对象
ajax
Spring
友情链接
申请
范明明
庄严博客
Mx
陶小桃Blog
虫洞