🚀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 设置成员变量,这种没有任何成员变量的类是线程安全的。
✨由于成员变量保存的数据也可以称为状态信息,因此没有成员变量就称之为【无状态】。