单例模式
核心作用:保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
常见的应用场景:
- Windows的任务管理器。
- Windows的回收站。
- 在项目中,读取配置文件的类,一般也是只有一个对象,没有必要每次都去new对象读取。
- 数据库连接池一般也是单例模式。
- Servlet编程中的每个Servlet也是单例的。
- …
优点:
- 由于单例模式的类只生成一个实例对象,减少了系统性能的开销。
- 单例模式可以在系统设置全局的访问点,优化共享资源访问。
常见的五种单例模式实现方式:
- 饿汉式(线程安全,调用效率高,不能延时加载)
- 懒汉式(线程安全,电泳效率不高,可以延时加载)
- DCL懒汉式(由于JVM底层内部模型的原因,偶尔会出现问题,不建议使用)
- 饿汉式改进:静态内部类式(线程安全,调用效率高,可延时加载)
- 枚举单利(线程安全,调用效率高,不能延时加载)
饿汉式
//饿汉式单例
public class SingletonDemo01 {
//缺点:当我们很久没有使用getInstance()方法的时候 也会开辟成员变量的空间,浪费空间
private byte[] data1 = new byte[1024];
//1.私有化构造器
private SingletonDemo01() {
}
//2.类初始化的时候就立即加载
private static SingletonDemo01 instance = new SingletonDemo01();
//3.提供获取该对象的方法,没有synchronized,效率高
public static SingletonDemo01 getInstance() {
return instance;
}
}
//测试类
class SingletonDemo01Test{
public static void main(String[] args) {
SingletonDemo01 instance = SingletonDemo01.getInstance();
SingletonDemo01 instance1 = SingletonDemo01.getInstance();
System.out.println(instance == instance1);//true
}
}
这种方式实现也很简单,但是有个缺点,就是当我们很久都不使用该它的时候,成员变量的空间开辟了不会得到使用,就会造成空间的浪费,懒汉式就能对这问题进行解决。
懒汉式
//懒汉式单例
public class SingletonDemo02 {
//1.私有化构造器
private SingletonDemo02() {
}
//2.类初始化的时候,不立即加载该对象
private static SingletonDemo02 instance;
//3.提供获取该对象的方法,如果已加载就直接返回,未加载就先实例化,添加synchronized保证线程安全,效率较低
public static synchronized SingletonDemo02 getInstance(){
if (instance==null){
instance = new SingletonDemo02();
}
return instance;
}
}
class SingletonDemo02Test{
public static void main(String[] args) {
SingletonDemo02 instance = SingletonDemo02.getInstance();
SingletonDemo02 instance1 = SingletonDemo02.getInstance();
System.out.println(instance == instance1);//true
}
}
由于在方法上添加了synchronized关键字,所以这种方式的效率就相对较低了,DCL懒汉式又是这种方式的优化。
DCL懒汉式
//DCL懒汉式(DCL:double click lock 双重检查锁)
public class SingletonDemo03 {
//1.私有化构造器
private SingletonDemo03() {
}
//2.类初始化的时候,不立即加载该对象
// 添加volatile保证有线程对该变量修改时,另一个线程读取时,缓存就失效了,直接从内存中读取
private volatile static SingletonDemo03 instance;
//3.提供获取该对象的方法,如果已加载就直接返回,未加载就先实例化
public static SingletonDemo03 getInstance() {
if (instance == null) {
//当对象未实例化时再锁住,不是对方法加锁 效率提高
synchronized (SingletonDemo03.class) {
//再次进行判断是否为空,锁住了再次判断,更加安全
if (instance == null) {
instance = new SingletonDemo03();
}
}
}
return instance;
}
}
class SingletonDemo03Test {
public static void main(String[] args) {
SingletonDemo03 instance = SingletonDemo03.getInstance();
SingletonDemo03 instance1 = SingletonDemo03.getInstance();
System.out.println(instance == instance1);//true
}
}
这种方式看似没有任何问题,但是这不是原子性的操作,在极端的条件下,还是容易出现问题,就是多条线程如果同时进入new对象的操作前后,就极有可能破坏单例模式。
静态内部类
//静态内部类实现
public class SingletonDemo04 {
//1.私有化构造器
private SingletonDemo04(){
}
//2.通过静态内部类创建单例对象
//当该内部类被调用时,静态内部类才加载
private static class InnerClass{
//通过static保证只有一份 final保证不能修改
private static final SingletonDemo04 instance = new SingletonDemo04();
}
//3.提供外部访问方法
public static SingletonDemo04 getInstance(){
return InnerClass.instance;
}
}
class SingletonDemo04Test {
public static void main(String[] args) {
SingletonDemo04 instance = SingletonDemo04.getInstance();
SingletonDemo04 instance1 = SingletonDemo04.getInstance();
System.out.println(instance == instance1);//true
}
}
虽然说这种方式能够解决上面的问题,但是如果我们使用反射,就能忽略private关键字,从而实现通过反射破坏该单例模式。
SingletonDemo04 instance = SingletonDemo04.getInstance();
SingletonDemo04 instance1 = SingletonDemo04.getInstance();
System.out.println(instance == instance1);//true
//反射创建
Class<SingletonDemo04> singletonDemo04Class = SingletonDemo04.class;
Constructor<SingletonDemo04> constructor = singletonDemo04Class.getDeclaredConstructor(null);
//关闭安全检查
constructor.setAccessible(true);
SingletonDemo04 instance2 = constructor.newInstance(null);
System.out.println(instance1 == instance2);//false
枚举单例
//枚举单例
public enum SingletonDemo05 {
INSTANCE;
}
class SingletonDemo05Test{
public static void main(String[] args) {
SingletonDemo05 instance = SingletonDemo05.INSTANCE;
SingletonDemo05 instance1 = SingletonDemo05.INSTANCE;
System.out.println(instance == instance1);//true
}
}
枚举是不能被反射的,而且枚举是天然的单例,线程安全,调用效率高,枚举单例在目前来说还是比较推荐的一种方式。只是枚举单例不能实现延时加载。
以上五种方式都要根据实际情况来实际选择。