单例对象(Singleton)是一种常用的设计模式。在Java应用中,单例对象能保证在一个JVM中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了new操作符,降低了系统内存的使用频率,减轻GC压力。
-
设计方式
1. 饿汉式方式
public class Student5 {
// 构造私有
private Student5() {}
//内部创建好实例对象
private static Student5 student = new Student5();
/* 获取实例 */
public static Student5 getSingletonInstance() {
return SingletonFactory.student;
}
}
2. 懒加载方式
1、双重检测锁解决线程安全问题
public class Student5 {
private Student5() {}
//需要的时候创建
private static Student5 student = null;
/* 获取实例 */
public static Student5 getSingletonInstance() {
if(student ==null){
student = new Student();
}
return student;
}
// 构造私有
private Student1() {}
}
懒加载的好处毋庸置疑,就是需要的时候才创建对象,不需要就不会创建,相对节省内存空间,但是也消耗了一定性能。这里还存在线程安全问题,可以使用双重检测锁去解决问题:
//这里加volatile关键作用是为了防止指令重排序
private static volatile DoubleCheckedLock instance;
public static DoubleCheckedLock getInstance(){
if (null == instance){
synchronized (DoubleCheckedLock.class){
if (null == instance){
instance = new DoubleCheckedLock();
}
}
}
return instance;
}
2、内部类利用jvm保证线程安全问题
其实也可以使用jvm来保证其线程安全,而懒加载的机制则是依靠虚拟机规范制度的类“初始化”规则保证,这里可以参考下这篇文章:https://www.cnblogs.com/niuyourou/p/11892617.html
public class Singleton {
private static class SingletonHolder {
private static Singleton singleton = new Singleton();
}
private Singleton() {
}
public static Singleton newInstance() {
return SingletonHolder.singleton;
}
}
设计单例模式需要注意的问题:
1、饿汉式的线程安全问题;volatile防指令重排序问题
2、构造方法私有
3、反射攻击或者反序列化攻击
// 1、反射攻击
// 返回false,违背了单例模式的原则!!!
public static void main(String[] args) throws Exception {
Singleton singleton = Singleton.getInstance();
Constructor<Singleton> constructor = Singleton.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton newSingleton = constructor.newInstance();
System.out.println(singleton == newSingleton);
}
// 2、反序列化攻击
//引入序列化工具包依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
//单例类实现java.io.Serializable接口,经过序列化和反序列化后引用变了,返回false
public static void main(String[] args) {
Singleton instance = Singleton.getInstance();
byte[] serialize = SerializationUtils.serialize(instance);
Singleton newInstance = SerializationUtils.deserialize(serialize);
System.out.println(instance == newInstance);
}
解决问题的办法:枚举天生保证序列化单例
针对以上问题,单例的枚举实现在《Effective Java》中有提到,因为其功能完整、使用简洁、无偿地提供了序列化机制、在面对复杂的序列化或者反射攻击时仍然可以绝对防止多次实例化等优点,单元素的枚举类型被作者认为是实现Singleton的最佳方法。
public enum DataSourceEnum {
DATASOURCE;
private DBConnection connection = null;
private DataSourceEnum() {
connection = new DBConnection();
}
public DBConnection getConnection() {
return connection;
}
}
网友评论