• 多线程之不可变对象


    1 日期转换的问题

    SimpleDateFormat是线程不安全类,在对线程环境下,会出现错误.

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    for (int i = 0; i < 10; i++) {
         new Thread(() -> {
         try {
             log.debug("{}", sdf.parse("2022-08-07"));
         } catch (Exception e) {
             log.error("{}", e);
         }
         }).start();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    出现 java.lang.NumberFormatException 或者出现不正确的日期解析结果.

    19:10:40.859 [Thread-2] c.TestDateParse - {} 
    java.lang.NumberFormatException: For input string: "" 
     at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) 
     at java.lang.Long.parseLong(Long.java:601) 
     at java.lang.Long.parseLong(Long.java:631) 
     at java.text.DigitList.getLong(DigitList.java:195) 
     at java.text.DecimalFormat.parse(DecimalFormat.java:2084) 
     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 cn.cf.n7.TestDateParse.lambda$test1$0(TestDateParse.java:18) 
     at java.lang.Thread.run(Thread.java:748) 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2 解决方案

    1 同步锁

    加锁 ,保证每次只有一个线程去执行方法. 能解决问题,但是影响性能效率.

    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();
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    2 类不可变

    如果一个对象在不能够修改其内部状态(属性),那么它就是线程安全的,因为不存在并发修改.

    以Java8提供的新日期格式化类DateTimeFormatter.

    DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    for (int i = 0; i < 10; i++) {
         new Thread(() -> {
             LocalDate date = dtf.parse("2022-08-07", LocalDate::from);
             log.debug("{}", date);
         }).start();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    将对象置为不可变对象,是一种避免竞争的方式.

    3 不可变设计

    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
     
     // ...
     
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    其中使用final修饰了String对象, 且属性都是final的.

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

    保护性拷贝

    使用字符串时,也有一些跟修改相关的方法,比如 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    发现其内部是调用 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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

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

  • 相关阅读:
    View基础知识-位置大小和滑动
    洛谷C++简单题小练习day11—字母转换,分可乐两个小程序
    优秀的测试/开发程序员与普通的程序员对比......
    HTML+CSS网页设计期末课程大作业 【茶叶文化网站设计题材】web前端开发技术 web课程设计 网页规划与设计
    【Java】三大集合容器概述
    迅为iTOP-2K1000开发板龙芯中科国产64位Loognix系统工业核心主板
    LINQPad Premium 7.5.14 Crack
    一起来学Kotlin:概念:20. Kotlin open 关键字与类名、函数名和变量名的使用
    java datetime数据类型去掉时分秒
    中间件 - 分布式协调服务Zookeeper
  • 原文地址:https://blog.csdn.net/ABestRookie/article/details/126213241