《Java 并发编程》共享模型之不可变

简介: 《Java 并发编程》共享模型之不可变

🚀1. 日期转换的问题

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

public class Test {
    public static void main(String[] args) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                try {
                    System.out.println(sdf.parse("2022-04-26"));
                } catch (Exception e) {
                    System.out.println(e);
                }
            }).start();
        }
    }
}

因此,可能会出现 java.lang.NumberFormatException 或不正确的日期解析结果,如下所示:

java.lang.NumberFormatException: For input string: ".E0"
java.lang.NumberFormatException: For input string: "44E.144E1"
java.lang.NumberFormatException: For input string: "44E.144"
java.lang.NumberFormatException: For input string: ".20222022E"
java.lang.NumberFormatException: For input string: ""
java.lang.NumberFormatException: For input string: ""
java.lang.NumberFormatException: empty String
Mon Oct 26 00:00:00 CST 44
Tue Apr 26 00:00:00 CST 2022
Tue Apr 26 00:00:00 CST 2022

针对这个问题,可以使用同步锁 synchronized,但是会带来性能上的损失

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 20; i++) {
    new Thread(()->{
        synchronized (sdf) {
            try {
                System.out.println(sdf.parse("2022-04-26"));
            } catch (Exception e) {
                System.out.println(e);
            }
        }
    }).start();
}

🎈还有一种思路就是不可变

如果一个对象在不能够修改其内部状态(属性)的情况下,那么它就是线程安全的(不存在并发修改)。例如,在 Java 8 以后,提供了一个新的日期格式化类:

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
      new Thread(()->{
          LocalDate date = dtf.parse("2022-04-26", LocalDate::from);
          System.out.println(date);
      }).start();
}

查看 DateTimeFormatter 的源码,它是一个不可变类:

 * @implSpec
 * This class is immutable and thread-safe.
 *
 * @since 1.8
 */
public final class DateTimeFormatter {
  ...
}

🚀2. 不可变设计

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
}

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

可以看到,在 return 语句中,是调用了 String 的构造方法创建了一个字符串

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

构造函数如下

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,对内容进行复制。这种通过创建副本对象来避免共享的手段称为【保护性拷贝(defensive copy)】

🚀3. 无状态

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


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


相关文章
|
6天前
|
存储 NoSQL Java
一天五道Java面试题----第十一天(分布式架构下,Session共享有什么方案--------->分布式事务解决方案)
这篇文章是关于Java面试中的分布式架构问题的笔记,包括分布式架构下的Session共享方案、RPC和RMI的理解、分布式ID生成方案、分布式锁解决方案以及分布式事务解决方案。
一天五道Java面试题----第十一天(分布式架构下,Session共享有什么方案--------->分布式事务解决方案)
|
8天前
|
缓存 Java 数据处理
Java中的并发编程:解锁多线程的力量
在Java的世界里,并发编程是提升应用性能和响应能力的关键。本文将深入探讨Java的多线程机制,从基础概念到高级特性,逐步揭示如何有效利用并发来处理复杂任务。我们将一起探索线程的创建、同步、通信以及Java并发库中的工具类,带你领略并发编程的魅力。
|
10天前
|
Java 调度 开发者
Java并发编程:解锁多线程同步的奥秘
在Java的世界里,并发编程是提升应用性能的关键所在。本文将深入浅出地探讨Java中的并发工具和同步机制,带领读者从基础到进阶,逐步掌握多线程编程的核心技巧。通过实例演示,我们将一起探索如何在多线程环境下保持数据的一致性,以及如何有效利用线程池来管理资源。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你对Java并发编程有更深入的理解和应用。
|
13天前
|
算法 安全 Java
Java中的并发编程:从基础到高级
在Java的世界中,并发编程是一块基石,它允许多个操作同时进行,极大地提高了程序的效率和性能。本文将深入探讨Java并发编程的核心概念、实用工具和高级技术,旨在为读者提供一套完整的解决方案,以应对复杂的并发挑战。我们将从线程的基础讲起,逐步过渡到线程池的使用,最后探讨Java并发包中的强大工具,如CyclicBarrier和Semaphore等。
17 4
|
11天前
|
算法 安全 Java
探索Java中的并发编程:挑战与解决方案
【8月更文挑战第9天】 在Java世界中,并发编程是一个既令人兴奋又充满挑战的领域。它不仅为开发者提供了提高应用程序性能和响应性的机会,还带来了诸如数据一致性、线程安全和死锁等复杂问题。本文旨在通过分析Java并发的核心概念、常见并发模式及其实现方式,探讨如何在Java中有效地管理多线程环境,同时识别并解决并发编程过程中可能遇到的常见问题。
|
12天前
|
网络协议 Java 关系型数据库
16 Java网络编程(计算机网络+网络模型OSI/TCP/IP+通信协议等)
16 Java网络编程(计算机网络+网络模型OSI/TCP/IP+通信协议等)
41 2
|
16天前
|
Java API 开发者
Java中的并发编程:解锁多线程的潜力
在数字化时代的浪潮中,并发编程已成为软件开发的核心技能之一。本文将深入探讨Java中的并发编程概念,通过实例分析与原理解释,揭示如何利用多线程提升应用性能和响应性。我们将从基础的线程创建开始,逐步过渡到高级的同步机制,并探讨如何避免常见的并发陷阱。读者将获得构建高效、稳定并发应用所需的知识,同时激发对Java并发更深层次探索的兴趣。
27 2
|
18天前
|
安全 NoSQL Java
|
5天前
|
安全 Java 开发者
Java中的并发编程:从基础到高级
本文将深入浅出地介绍Java并发编程的核心概念,包括线程安全、同步机制、锁和线程池等。我们将从简单的多线程示例出发,逐步深入到高级并发工具类的应用,最后探讨性能优化技巧。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的知识和实践建议。
5 0
|
12天前
|
Java 数据库
Java中的并发编程:深入理解线程池
在Java的并发编程领域,线程池是提升性能和资源管理的关键工具。本文将通过具体实例和数据,探讨线程池的内部机制、优势以及如何在实际应用中有效利用线程池,同时提出一个开放性问题,引发读者对于未来线程池优化方向的思考。
31 0