单例模式
单例模式
单例模式是java中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象方式,可以直接访问,不需要实例化该类的对象。
1.单例模式的实现
单例设计模式分为两种:
- 饿汉式: 类加载就会导致该单实例对象被创建
- 懒汉式: 类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建
饿汉式-静态变量方式
/***
* 饿汉式01-静态变量方式
*/
public class EHanOne {
//私有构造方法
private EHanOne() {}
// 在成员位置创建该类的对象
private static EHanOne instance = new EHanOne();
//对外提供静态方法获取该对象
public static EHanOne getInstance() {
return instance;
}
}
说明:该方式在成员位置声明EHanOne类型的静态变量,并创建EHanOne类的对象instance。instance对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费
饿汉式-静态代码块方式
/***
* 饿汉式-在静态代码块中创建该类对象
*/
public class EHanTwo {
//私有构造方法
private EHanTwo() {}
//在成员位置创建该类的对象
private static EHanTwo instance;
static {
instance = new EHanTwo();
}
//对外提供静态方法获取该对象
public static EHanTwo getInstance() {
return instance;
}
}
说明:该方式在成员位置声明EHanTwo类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方法1基本上一样,当然也存在内存浪费的问题。
懒汉式-线程不安全
/***
* 懒汉式-线程不安全
*/
public class LanHanOne {
//私有构造方法
private LanHanOne() {}
//对外提供静态方法获取
private static LanHanOne instance;
//对外提供静态方法获取该对象
public static LanHanOne getInstance() {
if(instance == null) {
instance = new LanHanOne();
}
return instance;
}
}
说明:从上面代码中我们可以看出该方式在成员位置声明LanHanOne类型的静态变量,并没有进行对象的赋值操作,那么什么时候赋值的呢?当调用getInstance()方法获取Singleton类的对象的时候猜创建LanHanOne类的对象,这样就实现了懒加载的效果。但是,如果是多线程的环境,会出现线程安全问题。
懒汉式-线程安全
/***
* 懒汉式-线程安全
*/
public class LanHanTwo {
//私有构造方法
private LanHanTwo() {}
//在成员位置创建该类对象
private static LanHanTwo instance;
//对外提供静态方法
public static synchronized LanHanTwo getInstance() {
if (instance == null) {
instance = new LanHanTwo();
}
return instance;
}
}
说明:该方式也实现类懒加载效果,同时解决了线程安全问题。但是在getInstance()方法上添加了synchronized关键字,导致该方法的执行效果特别低。从上面代码我们可以看出,其实就是在初始化instance的时候才会出现线程安全问题,一旦初始化完成就不存在了。
懒汉式-双重检查锁
再来讨论一下懒汉模式中加锁的问题,对于getInstance()方法来说,绝大部分的操作都是读操作,读操作是线程安全的,所以我们没必要让每个线程必须持有锁才能调用该方法,我们需要调整加锁的时机。由此也产生了一种新的实现模式:双重检查锁模式。
/***
* 懒汉式-双重检查锁
*/
public class LanHanThree {
// 私有构造方法
private LanHanThree() {}
private static LanHanThree instance;
public static LanHanThree getInstance() {
// 第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例
if(instance == null) {
synchronized (LanHanThree.class) {
//抢到锁之后再次判断是否为null
if (instance == null){
instance = new LanHanThree();
}
}
}
return instance;
}
}
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题,上面的双重检测模式看上去完美无缺,其实是存在问题,在多线程的情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针异常的问题,只需要使用volatile关键字,volatile关键字可以保证可见性和有序性。
/***
* 双重检查方式
*/
public class LanHanFour {
//私有构造方法
private LanHanFour() {}
private static volatile LanHanFour instance;
//对外提供静态方法获取该对象
public static LanHanFour getInstance() {
//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实际
if (instance == null) {
synchronized (LanHanFour.class) {
//抢到锁之后再次判断是否为空
if (instance == null) {
instance = new LanHanFour();
}
}
}
return instance;
}
}
小结:添加volatile关键字之后的双重检查锁模式是一种比较好的单例实现模式,能够保证在多线程的情况下,线程安全也不会有性能问题。
懒汉式-静态内部类方式
静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类,只有内部类的属性/方法调用时才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序。
public class LanHanFive {
//私有构造方法
private LanHanFive(){}
private static class SingletonHolder{
private static final LanHanFive INSTANCE = new LanHanFive();
}
//对外提供静态方法获取该对象
public static LanHanFive getInstance() {
return SingletonHolder.INSTANCE;
}
}
说明: 第一次加载LanHanFive类时不会去初始化INSTANCE,只有第一次调用getInstance,虚拟机加载SingletonHolder,并初始化INSTANCE,这样不仅能确保线程安全,也能保证Singleton类的唯一性。
2.存在的问题
2.1 案例1-序列化与反序列化
SingletonOne类
public class SingletonOne implements Serializable {
//私有构造方法
private SingletonOne() {}
private static class SingletonHolder {
private static final SingletonOne INSTANCE = new SingletonOne();
}
//对外提供静态方法获取该对象
public static SingletonOne getInstance(){
return SingletonHolder.INSTANCE;
}
}
Test类
public class TestOne {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// writeObject2File();
SingletonOne s1 = readObjectFromFile();
SingletonOne s2 = readObjectFromFile();
//判断两个反序列化的对象是否是同一个对象
System.out.println(s1 == s2);
}
private static SingletonOne readObjectFromFile() throws IOException, ClassNotFoundException {
//创建对象输入流对象
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\1.txt"));
//第一个读取Singleton对象
SingletonOne instance = (SingletonOne) ois.readObject();
return instance;
}
public static void writeObject2File() throws IOException {
//获取SingletonOne对象
SingletonOne instance = SingletonOne.getInstance();
//创建对象输出流
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\1.txt"));
//将instance对象写出文件中
oos.writeObject(instance);
}
}
结果
运行结果为false,说明序列化与反序列化已经破坏了单例设计模式。
2.2 案列2-反射
singletonTwo类
public class SingletonTwo {
//私有构造方法
private SingletonTwo(){}
private static volatile SingletonTwo instance;
//对外提供静态方法获取该对象
public static SingletonTwo getInstance() {
if (instance !=null) {
return instance;
}
synchronized (SingletonTwo.class) {
if (instance != null) {
return instance;
}
instance = new SingletonTwo();
return instance;
}
}
}
Test类
public class TestTwo {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//获取Singleton类的字节码对象
Class<SingletonTwo> clazz = SingletonTwo.class;
//获取Singleton类的私有无参构造方法对象
Constructor<SingletonTwo> constructor = clazz.getDeclaredConstructor();
//取消访问检查
constructor.setAccessible(true);
//创建Singleton类的对象s1
SingletonTwo s1 = constructor.newInstance();
SingletonTwo s2 = constructor.newInstance();
//判断通过反射创建两个singleton对象是否是同一个对象
System.out.println(s1 == s2);
}
}
结果
上面代码运行结果是false,表明反射已经打破了单例设计模式。