《Java 并发编程》专栏索引
《Java 并发编程》进程与线程
《Java 并发编程》共享模型之管程
《Java 并发编程》共享模型之内存
《Java 并发编程》共享模型之无锁
《Java 并发编程》共享模型之不可变
《Java 并发编程》线程池
在运行下面的代码时,由于 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 {
...
}
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 的使用
保护性拷贝
以 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)】
✨在 Web 阶段学习时,设计 Servlet 时为了保证其线程安全,都会有这样的建议,不要为 Servlet 设置成员变量,这种没有任何成员变量的类是线程安全的。
✨由于成员变量保存的数据也可以称为状态信息,因此没有成员变量就称之为【无状态】。