java.util.Date
的可变性
- 历史原因:
java.util.Date
类是在 Java 早期版本中设计的,当时的多线程编程还没有像现在这样普遍和复杂。因此,设计者可能更注重灵活性和性能,而没有过多考虑线程安全问题。 - 向后兼容:一旦一个类被广泛使用,改变其行为(例如将其变为不可变类)可能会破坏现有的代码。因此,即使后来发现了可变类的缺点,也很难进行大的改动。
内部结构
java.util.Date
类内部使用一个长整型变量 fastTime
来存储时间戳(以毫秒为单位)。这个变量是可以被修改的,因此 Date
对象的状态可以改变。
public class Date implements Serializable, Cloneable, Comparable<Date> {
private transient long fastTime;
// 其他字段和方法
}
修改方法
Date
类提供了多种方法来修改其内部状态,例如 setTime
方法:
public void setTime(long time) {
fastTime = time;
}
为什么可变类在多线程中不安全
共享状态
在多线程环境中,多个线程可能会同时访问和修改同一个 Date
对象的内部状态。这会导致竞态条件(race conditions),使得结果不可预测。
示例
假设你有以下代码在一个多线程环境中运行:
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DateExample {
private static final Date date = new Date();
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
synchronized (date) {
date.setTime(System.currentTimeMillis());
System.out.println(date);
}
});
}
executor.shutdown();
}
}
在这个例子中,多个线程同时修改同一个 Date
对象的时间戳。即使使用了 synchronized
关键字来同步对 date
的访问,仍然可能存在竞态条件,因为 System.currentTimeMillis()
和 date.setTime()
是两个独立的操作。
解决方案
1. 使用不可变类
java.time
包:Java 8 引入了java.time
包,其中的类(如LocalDate
、LocalTime
、LocalDateTime
等)是不可变的,因此在多线程环境中是安全的。
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class LocalDateTimeExample {
private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
LocalDateTime now = LocalDateTime.now();
System.out.println(formatter.format(now));
});
}
executor.shutdown();
}
}
2. 使用 ThreadLocal
ThreadLocal
:为每个线程提供一个独立的Date
实例,避免共享状态。
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class DateThreadLocalExample {
private static final ThreadLocal<SimpleDateFormat> sdfThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
SimpleDateFormat sdf = sdfThreadLocal.get();
Date now = new Date();
System.out.println(sdf.format(now));
});
}
executor.shutdown();
}
}
总结
- 可变类:提供灵活性和性能优势,但可能在多线程环境中导致不安全问题。
- 不可变类:在多线程环境中是安全的,但可能在某些场景下牺牲了一定的灵活性和性能。
- 解决方案:使用
java.time
包中的不可变类或使用ThreadLocal
来确保线程安全。