创建型设计模式之单例模式
单例模式
概述
单例模式保证一个对象在JVM中只能有一个实例,减少内存开销,避免对资源的多重占用。单例模式属于创建型模式。
在单例模式中,单例类只能有一个实例,必须由自己创建自己的唯一实例,给所有其他对象提供这一实例。
常见单例:
懒汉式:就是需要的才会去实例化,线程不安全。
饿汉式:就是当class文件被加载的时候,初始化,天生线程安全。
比如:ServletContext、ServletContextConfig、ApplicationContext等都是单例形式
优缺点
优点:
1.在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2.避免对资源的多重占用。
缺点:
1.没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
懒汉式
懒汉式单例模式是对象要在被使用时才会初始化,能解决饿汉式单例可能带来的内存浪费问题
注意:该方式在多线程中使用存在线程安全问题。
public class Singleton {
//需要时才会被实例化
private static Singleton singleton;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
使用synchronized
关键字使方法变成线程同步方法。
这种方法在线程数量较多情况下,如果CPU压力过大,则会导致大批线程阻塞,从而导致程序性能大幅下降。
public class Singleton {
//需要时才会被实例化
private static Singleton singleton;
private Singleton() {
}
synchronized public static Singleton getSingleton() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
public static void main(String[] args) {
Singleton sl1 = Singleton.getSingleton();
Singleton sl2 = Singleton.getSingleton();
System.out.println(sl1 == sl2);
}
使用双重检查锁的的单例模式可进一步提升性能。
public class Singleton {
//需要时才会被实例化
private static Singleton singleton;
private Singleton() {}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
//检查是否要重新创建实例
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
饿汉式
饿汉式单例模式在类加载的时候就立即初始化,并且创建单例对象。它绝对线程安全,在线程还没出现以前就实例化了,不可能存在访问安全问题。
适用于单例对象较少的情况。如果系统中有大批量的单例对象存在,那系统初始化是就会导致大量的内存浪费。
优点:能保证绝对线程安全、执行效率比较高
缺点:所有对象类加载的时候就实例化
public class Singleton{
//当class文件被加载初始化
private static Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getSingleton() {
return singleton;
}
}
public static void main(String[] args) {
Singleton singleton1 = Singleton.getSingleton();
Singleton singleton2 = Singleton.getSingleton();
System.out.println(singleton1 == singleton2);
}
上述是标准写法,还有另外一种写法,利用静态代码块的机制
public class Singleton{
//当class文件被加载初始化
private static Singleton singleton;
static{
singleton = new Singleton()
}
private Singleton() {}
public static Singleton getSingleton() {
return singleton;
}
}
其他实现方式
静态内部类实现单例
由于饿汉式单例模式存在内存浪费问题,懒汉式单例模式有
synchronized
性能问题。
实际上可以从类的初始化角度,采用静态内部类的方式,利用内部类一定是要在方法调用之前初始化的特点,巧妙避免线程安全问题
public class Singleton {
private Singleton() {
if (LazySingleton.INSTANCE != null) {
throw new RuntimeException("非法访问");
}
}
/**
* 在返回结果前先加载内部类
*/
private static Singleton getInstance() {
return LazySingleton.INSTANCE;
}
/**
* 默认不加载
*/
private static class LazySingleton {
private static final Singleton INSTANCE = new Singleton();
}
}
ThreadLocal实现线程单例
ThreadLocal不能保证其创建的对象是全局唯一的,但是能保证在单个线程中是唯一的,天生是线程安全的
单例模式为了线程安全会给方法加锁,以时间换空间。ThreadLocal将所有的对象全部放在ThreadLocalMap中,为每个线程都提供一个对象,实际上是以空间换时间实现线程隔离。
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocaLInstance =
new ThreadLocal<ThreadLocalSingleton>() {
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton() {
}
public static ThreadLocalSingleton getInstance() {
return threadLocaLInstance.get();
}
public static void main(String[] args) {
ThreadLocalSingleton threadLocalSingleton1 = ThreadLocalSingleton.getInstance();
ThreadLocalSingleton threadLocalSingleton2 = ThreadLocalSingleton.getInstance();
System.out.println(threadLocalSingleton1 == threadLocalSingleton2);
}
}
注册式单例模式
注册式单例模式又称为登记式单例模式,将每一个实例都登记到某一个地方,使用唯一的标识获取实例。
注册式单例模式有两种:枚举式单例模式
、容器式单例模式
枚举式单例模式
枚举式单例模式是比较推荐的一种单例模式实现,写法优雅
注意:与饿汉式类似,在类加载时就将所有对象初始化,不适合大量创建单例对象的场景
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance() {
return INSTANCE;
}
public static void main(String[] args) {
EnumSingleton instance1 = EnumSingleton.getInstance();
EnumSingleton instance2 = EnumSingleton.getInstance();
System.out.println(instance1 == instance2);
}
}
容器式单例模式
容器式单例模式适用于需要大量创建单例对象的场景,便于管理。注意:它是非线程安全的。
public class ContainerSingleton {
private ContainerSingleton() {
}
private static Map<String, Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getInstance(String className) {
Object instance = null;
if (!ioc.containsKey(className)) {
try {
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
} catch (Exception e) {
e.printStackTrace();
}
return instance;
} else {
return ioc.get(className);
}
}
public static void main(String[] args) {
Object instance1 = ContainerSingleton.getInstance("cn.ybzy.demo.ContainerSingleton");
Object instance2 = ContainerSingleton.getInstance("cn.ybzy.demo.ContainerSingleton");
System.out.println(instance1 == instance2);
}
}
单例模式的破坏
反射破坏
如果一个单例模式的私有构造不做任何处理,就可以通过反射破坏单例模式
private Singleton() {}
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Class<Singleton> singletonClass = Singleton.class;
// 反射强制获取私有构造函数
Constructor<Singleton> declaredConstructor = singletonClass.getDeclaredConstructor(null);
// 强制访问私有构造函数
declaredConstructor.setAccessible(true);
// 暴力调用私有构造函数进行初始化
Singleton singleton1 = declaredConstructor.newInstance();
Singleton singleton2 = declaredConstructor.newInstance();
System.out.println(singleton1 == singleton2);
}
在私有构造函数中添加限制处理,防止实例多次重复创建。
private Singleton() {
if (LazySingleton.INSTANCE != null) {
throw new RuntimeException("非法访问");
}
}
序列化破坏
序列化是将一个创建好的对象写入磁盘,下次使用时再从磁盘中读取对象并进行反序列化,将其转化为内存对象。由于反序列化后的对象会重新分配内存,即重新创建。如果序列化的目标对象为单例对象,就会破坏单例
public class SeriableSingleton implements Serializable {
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton() {
}
public static SeriableSingleton getInstance() {
return INSTANCE;
}
// private Object readResolve(){ return INSTANCE;}
public static void main(String[] args) {
SeriableSingleton s1 = SeriableSingleton.getInstance();
SeriableSingleton s2 = null;
FileOutputStream fos = null;
try {
fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s1);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s2 = (SeriableSingleton) ois.readObject();
ois.close();
System.out.println(s1 == s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
只需要增加一个叫
readResolve
方法返回实例就可以解决单例模式被序列化破坏的问题。
关键核心在于ObjectInputStream
中readObject(Class<?> type)
方法调用readObject0
方法中的switch (tc)
判断处理。
注意:实际上还是会实例化多次,只不过新创建的对象没有被返回而已。如果创建对象的发生频率加快,则内存分配开销也会随之增大
private Object readResolve(){ return INSTANCE;}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/136914.html