单例模式
定义:保证一个类仅有一个实例,并提供一个全局访问点
类型:创建型
适用场景
想确保任何情况下都绝对只有一个实例
优点
- 在内存里只有一个实例,减少了内存开销
- 可以避免对资源的多重占用
- 设置全局访问点,严格控制访问
缺点
- 没有接口,扩展困难
重点
- 私有构造器
- 线程安全
- 延迟加载
- 序列化和反序列化安全
- 反射
懒汉式
使用的时候才初始化
public class LazySingleton {
// 初始化不创建
private static LazySingleton lazySingleton = null;
// 私有构造器
private LazySingleton() {
}
// 获取lazysingleton对象的方法
public synchronized static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
在多线程下会有问题,在run模式下打印出来结果是同一个对象是因为结果被最后一个线程覆盖了,是最新的对象,在debug模式下就会看到两个实例对象了。
progrem end Thread-0singleton.LazySingleton@42337127 Thread-1singleton.LazySingleton@4233712
改进
使用synchronized同步锁
public synchronized static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
// synchronized类代码块,与上面的写法效果一致
public static LazySingleton getInstance(){
synchronized (LazySingleton.class){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
}
return lazySingleton;
}
在方法上加synchronized同步锁或是用同步代码块对类加同步锁,此种方式虽然解决了多个实例对象问题,但是该方式运行效率却很低下,下一个线程想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行。
继续改进,双重检查
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() {
}
}
只有一个线程去获得InnerClass这个静态内部类的初始化锁去初始化
只有一个线程对内部类进行类加载
饿汉式
在类加载的时候就创建,只会有一个线程进行类加载
public class HungrySingleton{
private final static HungrySingleton hungrySingleton=new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
声明为final必须在类加载的时候就初始化好,被定义为static字段或在静态代码块中初始化
public class HungrySingleton{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
序列化破坏单例模式
将对象序列化到文件中,然后再从文件中取出来,这两个对象还是一个对象吗?
先让对象实现Serializable接口
public class HungrySingleton implements Serializable{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
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);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
com.geely.design.pattern.creational.singleton.HungrySingleton@1973e9b com.geely.design.pattern.creational.singleton.HungrySingleton@1663380 false
发现序列化,然后反序列化得到的不是同一个对象为了解决这个问题,需要再添加一个方法
public class HungrySingleton implements Serializable{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
private Object readResolve(){
return hungrySingleton;
}
}
com.geely.design.pattern.creational.singleton.HungrySingleton@1973e9b com.geely.design.pattern.creational.singleton.HungrySingleton@1973e9b true
添加了一个readResolve方法,发现问题解决了。
解决反射攻击
构造器是私有的,但可以通过反射把构造器的权限打开
public class Test {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true);
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
HungrySingleton instance = HungrySingleton.getInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
com.geely.design.pattern.creational.singleton.HungrySingleton@8cf4c6 com.geely.design.pattern.creational.singleton.HungrySingleton@edcd21 false
在类加载时创建对象,可以这样修改其构造方法添加异常
饿汉式和内部静态类的单例都可以这样修改
public class HungrySingleton implements Serializable{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
对于懒汉式这种不是在类加载时创建对象的,无法避免反射攻击
因为懒汉式避免多线程的时候是在方法中避免的,但用反射的时候根本不用通过这个方法创建,想啥时候创建啥时候创建。但在类加载的时候有class锁避免多线程,所以可以避免反射攻击。
枚举
不受序列化影响
不能被反射
public enum EnumInstance {
INSTANCE;
private Object data;
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static EnumInstance getInstance(){
return INSTANCE;
}
}