文章目录
- 一、单例模式
- 1.1单例模式定义
- 1.2 单例模式的特点
- 二、实现单例模式的方式
- 2.1 饿汉式
- 2.2 懒汉式
- 2.3 双重检查锁:
- 2.4 静态内部类
- 2.5 枚举实现(防止反射攻击):
一、单例模式
1.1单例模式定义
单例模式确保系统中某个类只有一个实例,并提供一个访问它的全局访问点。主要解决一个全局使用的类频繁地创建与销毁,控制实例数目,节省系统资源。
1.2 单例模式的特点
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类必须给所有其他对象提供这一实例
- 单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性
单例的四大原则:
- 构造私有
- 以静态方法或者枚举返回实例
- 确保实例只有一个,尤其是多线程环境
- 确保反序列换时不会重新构建对象
二、实现单例模式的方式
2.1 饿汉式
饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以本身就是线程安全的。
Singleton 通过将构造方法限定为 private 避免了类在外部被实例化,在同一个虚拟机范围内,Singleton 的唯一实例只能通过 getInstance()方法访问。(事实上,通过 Java 反射机制是能够实例化构造方法为 private 的类的,会使 Java单例实现失效)
/*** @Author huang.bX* @Date 2021/7/21*/
public class SingletonTest01 {public static void main(String[] args) {Hungry instance = Hungry.getInstance();Hungry instance1 = Hungry.getInstance();Hungry instance2 = Hungry.getInstance();System.out.println(instance.getClass());System.out.println(instance1.getClass());System.out.println(instance2.getClass());}
}//饿汉式
class Hungry {//1构造器私有化,外部不能直接newprivate Hungry() {}//2本类的内部创建实例private final static Hungry hungry = new Hungry();//提供一个全局访问点共有的静态方法 返回实例对象public static Hungry getInstance(){return hungry;}
}
2.2 懒汉式
该示例虽然用延迟加载方式实现了懒汉式单例,但在多线程环境下会产生多个 Singleton 对象;
/*** @Author huang.bX* @Date 2021/7/21*/
public class SingletonTest03 implements Runnable {@Overridepublic void run(){for (int i=1;i<1000;i++){LazyMan lazyMan=LazyMan.getInstance();System.out.println(lazyMan.hashCode());}}public static void main(String[] args) {/** LazyMan instance1 = LazyMan.getInstance();* LazyMan instance2 = LazyMan.getInstance();* System.out.println(instance1.hashCode()==instance2.hashCode());*/new Thread(new SingletonTest03()).start();new Thread(new SingletonTest03()).start();new Thread(new SingletonTest03()).start();}
}class LazyMan{private LazyMan(){}private static LazyMan lazyMan;//public static LazyMan getInstance()线程不安全public static synchronized LazyMan getInstance(){if (lazyMan==null){lazyMan = new LazyMan();}return lazyMan;}
}
2.3 双重检查锁:
使用双重检查锁进一步做了优化,可以避免整个方法被锁,只对需要锁的代码部分加锁,可以提高执行效率。
/*** @Author huang.bX* @Date 2021/7/21*/
public class SingletonTest05 {public static void main(String[] args) {DoubleLock instance1 = DoubleLock.getInstance();DoubleLock instance2 = DoubleLock.getInstance();System.out.println(instance1.hashCode()==instance2.hashCode());}
}class DoubleLock{private static volatile DoubleLock doubleLock;private DoubleLock(){}public static DoubleLock getInstance(){if (doubleLock==null){synchronized (DoubleLock.class){if (doubleLock==null){doubleLock = new DoubleLock();}}}return doubleLock;}
}
2.4 静态内部类
这种方式引入了一个内部静态类(static class),静态内部类只有在调用时才会加载,它保证了 Singleton 实例的延迟初始化,又保证了实例的唯一性。它把 singleton 的实例化操作放到一个静态内部类中,在第一次调用 getInstance() 方法时,JVM 才会去加载 InnerObject 类,同时初始hsingleton 实例,所以能让 getInstance() 方法线程安全。特点是:即能延迟加载,也能保证线程安全。静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。
/*** @Author huang.bX* @Date 2021/7/21*/
public class SingletonTest06 {public static void main(String[] args) {StaticInClass instance1 = StaticInClass.getInstance();StaticInClass instance2= StaticInClass.getInstance();System.out.println(instance1.hashCode()==instance2.hashCode());}
}class StaticInClass{private static volatile StaticInClass staticInClass;//构造器私有化private StaticInClass(){}//定义一个静态内部类,该类中有一个静态属性private static class Inner{private static final StaticInClass INSTANCE = new StaticInClass();}public static synchronized StaticInClass getInstance(){return Inner.INSTANCE;}}
2.5 枚举实现(防止反射攻击):
事实上,通过 Java 反射机制是能够实例化构造方法为 private 的类的。这也就是我们现在需要引入的枚举单例模式。
/*** @Author huang.bX* @Date 2021/7/21*/
public class SingletonTest07 {public static void main(String[] args) {Singleton instance1 = Singleton.INSTANCE;Singleton instance2 = Singleton.INSTANCE;System.out.println(instance1.hashCode());System.out.println(instance2.hashCode());System.out.println(instance2.hashCode()==instance1.hashCode());System.out.println(instance1.getClass());System.out.println(instance2.getClass());System.out.println(instance1.getDeclaringClass());}
}enum Singleton{INSTANCE;//属性public void say(){System.out.println("ok!");}
}