不可变对象
- 有一种对象只要发布了就是安全的,这就是不可变对象,最典型的例子就是String类的对象;
- 不可变对象是解决并发问题的一种思路;
不可变对象需要满足的条件
- 对象创建以后其状态就不能修改;
- 对象所有的域都是final类型;
- 对象是正确创建的(在对象创建期间,this引用没有溢出);
实现不可变对象的手段
- 将类声明为final;
- 将所有成员声明为私有的;
- 对变量不提供setter方法;
- 将所有可变的成员声明为final;
- 通过构造器初始化所有成员;
- 进行深度拷贝;
- 在getter方法中不返回对象本身,而是克隆对象,返回对象的拷贝;
final关键字
- 修饰类,类不能被继承,final类中的所有成员方法都会被隐式的声明为final方法,尽量不要将类设计为final类;
- 修饰方法:锁定方法不被继承类修改,一个类的private方法会隐式的被指定为final方法;
- 修饰变量
- 修饰基本类型变量:变量值在初始化之后就不能修改了;
- 修饰引用型变量:对其初始化之后便不能再将它指向另外的对象,但是可以允许修改里面的值;
- 修饰基本类型的方法参数:在方法中也不允许修改;
其他实现不可变对象的手段
Collections.unmodifiableXXX
- Collections.unmodifiableXXX: Collection, List, Set, Map...,只需将自己写的相应对象传入相应方法,示例如下:
- 其实现思想是:新建一个类UnmodifiableMap,然后把Map的初始值复制进去,对于Map接口定义的其他方法,其实现就是抛出异常;
import com.example.concurrency.annotations.ThreadSafe;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import java.util.Collections;
import java.util.Map;
@Slf4j
@ThreadSafe
public class ImmutableExample2 {
private static Map<Integer, Integer> map = Maps.newHashMap();
static {
map.put(1, 2);
map.put(3, 4);
map.put(5, 6);
map = Collections.unmodifiableMap(map);
}
public static void main(String[] args) {
map.put(1, 3);
log.info("{}", map.get(1));
}
}
输出:
Exception in thread "main" java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableMap.put(Collections.java:1457)
at com.example.concurrency.example.immutable.ImmutableExample2.main(ImmutableExample2.java:24)
Guava:ImmbutaleXXX
- 示例如下:
import com.example.concurrency.annotations.ThreadSafe;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
@ThreadSafe
public class ImmutableExample3 {
private final static ImmutableList<Integer> list = ImmutableList.of(1, 2, 3);
private final static ImmutableSet set = ImmutableSet.copyOf(list);
private final static ImmutableMap<Integer, Integer> map = ImmutableMap.of(1, 2, 3, 4);
private final static ImmutableMap<Integer, Integer> map2 = ImmutableMap.<Integer, Integer>builder()
.put(1, 2).put(3, 4).put(5, 6).build();
public static void main(String[] args) {
System.out.println(map2.get(3));
}
}
安全共享对象策略 - 总结
- 线程限制:一个被线层限制的对象,由线程独占,并且只能被占有它的线程修改;
- 共享只读:一个共享只读的对象,在没有额外同步的情况下,可以被多个线程并发访问,但是任何线程都不能修改它;
- 线程安全对象:一个线程安全的对象或者容器,在内部通过同步机制来保证线程安全,所以其他线程无需额外的同步就可以通过公共接口随意访问它;
- 被守护对象:被守护对象只能通过获取特定的锁来访问;