关键定义
一个类仅有一个实例,并提供一个全局访问点
适用场景
确保任何情况下只有一个实例
优点
- 减少资源开销
- 严格控制访问
缺点
没有抽象层,扩展性差
代码实现
懒汉式
public class LazySingleton {
private static LazySingleton lazySingleton = null;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
以上在多线程场景下将会有线程安全问题,下面通过双重锁检查从线程安全与性能方面优化单例模式
public class LazyDoubleCheckSingleton {
/*禁止指令重排序*/
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance(){
if(lazyDoubleCheckSingleton == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazyDoubleCheckSingleton == null){
/*分配内存、指向内存地址、对象初始化*/
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazyDoubleCheckSingleton;
}
}
静态内部类
虚拟机在类的初始化阶段会获取类的初始化锁,同步多个线程对一个类的初始化,基于这个特性,可以实现基于静态内部类线程安全的延迟初始化方案(静态内部类只有当被外部类调用到的时候才会初始化)
public class StaticInnerClassSingleton {
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance(){
return InnerClass.staticInnerClassSingleton;
}
private StaticInnerClassSingleton(){
}
}
饿汉式
public class HungrySingleton {
private final static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return hungrySingleton;
}
}
线程单例模式
此单例模式不能保证应用全局唯一,但能保证线程唯一
public class ThreadLocalInstance {
private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal = new ThreadLocal<ThreadLocalInstance>() {
@Override
protected ThreadLocalInstance initialValue() {
return new ThreadLocalInstance();
}
};
private ThreadLocalInstance() {
}
public static ThreadLocalInstance getInstance() {
return threadLocalInstanceThreadLocal.get();
}
}
序列化破坏单例模式
public static void main(String[] args) throws IOException, ClassNotFoundException {
HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
System.out.println(instance == newInstance);
}
以上代码执行结果为两个对象不相等,主要是因为读取对象时候执行了下面的关键逻辑
/*ObjectInputStream.java*/
private Object readOrdinaryObject(boolean unshared) throws IOException
{
...
obj = desc.isInstantiable() ? desc.newInstance() : null;
...
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod())
{
Object rep = desc.invokeReadResolve(obj);
...
}
return obj;
}
如果类实现了序列化接口,并且定义了以下方法,则对象为该方法返回值,否则反射新建一个对象
private Object readResolve(){
return hungrySingleton;
}
添加上述代码之后,反序列化后的对象与原对象相等
反射攻击解决方案
对于类加载过程创建单例对象的单例模式
private HungrySingleton(){
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
private StaticInnerClassSingleton(){
if(InnerClass.staticInnerClassSingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
以饿汉模式为例,运行以下代码后将抛出异常
public static void main(String[] args) throws IOException, ClassNotFoundException {
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton instance = (HungrySingleton) constructor.newInstance();
System.out.println(instance);
}
对于非类加载过程创建单例对象的单例模式,反射攻击是无法避免的
private LazySingleton(){
if(lazySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
以懒汉模式为例,即使对构造方法进行改造,但在多线程场景,先反射后创建,一样会破坏单例模式
推荐:枚举单例模式
public enum EnumInstance {
INSTANCE;
public static EnumInstance getInstance(){
return INSTANCE;
}
}
反序列化时通过类型与枚举对象名称获取唯一的枚举常量,避免反序列化破坏单例模式
/*ObjectInputStream.java*/
private Enum<?> readEnum(boolean unshared) throws IOException {
...
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
...
Enum<?> en = Enum.valueOf((Class)cl, name);
...
} catch (IllegalArgumentException ex) {
...
}
...
}
...
return result;
}
反射时将会抛出无法通过反射创建枚举类型
/*Constructor.java*/
public T newInstance(Object ... initargs) {
...
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
...
}