什么是不可变类?
不可变类为什么是线程安全的?
如果一个对象在不能修改其内部状态时,就代表了当前类不会存在对内部属性进行写的情况,也就是说它是线程安全的。
package com.bo.threadstudy.seven;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
@Slf4j
public class DateFormatTest {
//测试时间类的不可见性
public static void main(String[] args) {
//使用SimpleDateFormat报错
//extracted();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
TemporalAccessor parse = dateTimeFormatter.parse("1951-02-04");
log.debug("{}",parse);
}).start();
}
//线程安全,是多个线程改变某个字段时,造成的数据异常,不可变类是创建的新对象,保护性拷贝,也就没有线程安全的问题,其实就是不可能出现修改操作
//线程安全,是对同一个对象使用多线程修改时的问题,所以不可变类可以控制线程安全。
//虽然指令交错的现象还会发生,但是因为不会修改原有数据,所以是线程安全的,如果想控制执行顺序或者结果,采用CAS或者synchroonized,reetrantlock的方式
// BigDecimal bigDecimal = new BigDecimal("10000");
//
// for (int i = 0; i < 10; i++) {
// new Thread(() -> {
// BigDecimal subtract = bigDecimal.subtract(new BigDecimal("1000"));
// log.debug(subtract.toString());
// }).start();
// }
}
private static void extracted() {
//这种方式报错
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
for (int i = 0; i < 10; i++) {
new Thread(() -> {
Date format = null;
try {
format = simpleDateFormat.parse("1951-04-21");
} catch (ParseException e) {
e.printStackTrace();
}
log.debug("{}",format);
}).start();
}
}
}
因为我们平时接触的线程不安全,都是在多个线程进行操作时,并发对内部元素进行修改,发生了指令交错,最后这个内部元素被修改的出现了问题。
而不可变类,之所以不可变,是因为内部元素或者类,或者方法都设置为final类型,就拿属性举例,final修饰了,基础类型就不可能发生并发修改的情况。而引用类型,虽然可能会改变final所修饰对象的成员变量。但是在不可变类中,会有一个保护性拷贝的方式,让最终的结果是一个新拷贝的结果,而无法被多个线程并发修改。
拿String来举例吧,看看保护性拷贝,就像SubString方法,以及构造方法。
String的构造方法可以传入一个char数组。
这里采用了拷贝的方式,为什么要拷贝一下?这种就是保护性拷贝。因为String既然设计成不可变类,那么这个数组中的元素是不可以被改变的。如果只是赋值的话,这种赋值方式是一种浅拷贝的过程。假如我在外部对这个数组造成改变,那么String内部的数组也会发生改变,不可变就被破坏掉了。所以这里在构造函数中采用了保护性拷贝的方式。
substring方法则是new了个的字符串。也好理解,假如多个线程来进行操作时,相当于创建了两个新的字符串。虽然发生了指令交错,但并没有出现并发写的情况。也是保护性拷贝的一种方式,是线程安全的。
我原先还写了段代码,不可变类虽然是线程安全的,但要明白线程安全的概念,多个线程对共享数据发生并发写的情况造成数据异常,这种才是线程不安全。线程安全,不可变类确实满足线程安全的条件,没有对共享数据发生并发写操作。但是,不代表了结果就是对的。
BigDecimal虽然是线程安全的,不可变类。但是如果多个线程对该对象进行操作时,所有的线程取的都是10000这个数,最终都是9000。假如想让并发操作有序进行,还是得配合AtomicReference来进行使用。
注意线程安全的概念,不是说某个类线程安全了,那么多个线程操作同一对象的某个方法时,就什么控制都不需要做了。就像synfhronized,Reetrantlock以及CAS修饰的这种线程安全不控制也不会出现问题。不可变类是个例外,得控制执行顺序。