创建型设计模式—singleton(单例模式)

本文详细介绍了Java中的单例设计模式,包括其作用、核心思想和多种实现方式,如急加载、懒加载、懒汉式加锁、双重加锁、静态内部类以及枚举方式。每种实现方式的优缺点和线程安全性都有所阐述,特别是强调了在多线程环境下的单例实现策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、创建型设计模式的作用

关注点在与怎样创建对象,将创建对象和使用对象分离

二、核心思想:

单例模式的核心在于要保证内存里面始终只存在一个类对象。
主要从2个方面考虑:
1、必须私有化构造方法,防止在外部new对象
2、必须提供一个公有方法给外部调用,这个公有方法里面实现单例创建对象的逻辑

三、实现方式

1、利用类加载机制(急加载)

(1)、思想

	利用类加载机制,在类加载的时候,被static修饰的成员变量就会加载到内存中,
	并且在是类对象可以保证内存中只有一个。
	
	缺点在于在类加载的时候就会初始化对象,有些时候并不需要进行急加载,
	而是等到需要的时候再初始化。

(2)、步骤

 1. 私有化构造方法 		
 2. 声明static成员变量,在类加载的时候就new出对象
 3. 声明一个公有方法,直接返回这个static成员变量指向的对象

(3)、代码

//static急加载,利用类加载机制,实现单例
public class Singleton1 {

    //1、私有化构造方法,让外部不能new出对象
    private void singleton(){};

    //2、声明一个static修饰的私有对象,在类加载的时候,就实例化出来。
    private static Singleton1 SINGLETON= new Singleton1();

    //3、声明一个公有方法,用于返回实例化出来的单例对象
    public static Singleton1 getSingeton(){
        return SINGLETON;
    }

}

2、利用类加载机制(懒加载)

(1)、思想

和利用类加载机制(急加载)实现方式的区别在于,不用一加载类就初始化对象,
可以等到需要(公有方法被调用的时候)再初始化单例对象。

在多线程的情况下,可能会破坏单例。
当一个线程在if判断变量为null时挂起,另外一个线程进行判断,
同样会判断变量为null,这个时候两个线程都进入了非空判断逻辑,所以迟早会破坏单例。

(2)、步骤

 1. 私有化构造方法 	
 2. 声明static修饰的成员变量,先不初始化
 3. 声明公有方法,在方法里面先判断static成员变量是否为null,如果为null则初始化,
    如果不为null则直接返回。

(3)、代码

//static懒加载,利用类加载机制,实现单例,
// 多线程操作可能会有问题
// 因为可能第一个线程进入了if判断,
// 然后第二个线程开始执行,第二个线程还是会判断为null,抢占到锁之后进行new对象操作,这样就保证不了单例。
public class Singleton2 {

    //1、私有化构造方法,让外部不能new出对象
    private void singleton2(){};

    //2、声明static成员变量,类加载的时候先不实例化
    private static Singleton2 SINGLETON;

    //3、静态方法,判断static成员变量是否为null,如果为null初始化
    public static Singleton2 getSingeton(){

        if (SINGLETON==null){
            SINGLETON=new Singleton2();
        }
        return SINGLETON;
    }
    
}

3、懒汉式加锁

(1)、思想

在懒汉式的基础上,在if判断static变量为null的代码里面
加上synchronized锁。

缺点:
  在多线程的情况下,也可能会破坏单例。
  当一个线程在if判断变量为null时挂起,另外一个线程进行判断,
  同样会判断变量为null,这个时候两个线程都进入了非空判断逻辑,
  就算加锁也无法阻止,因为抢占到锁之后,还是都会执行new对象的操作。

(2)、步骤

 1. 私有化构造方法 声明static修饰的成员变量,先不初始化
 2. 声明公有方法,在方法里面先判断static成员变量是否为null,
 3. 如果不为null则直接返回。如果为null则加锁初始化

(3)、代码

//懒汉式加锁实现,在公有方法里面if判断是对象否为null,如果为null就加锁new对象,
// 这样还是有风险,
// 因为可能第一个线程进入了if判断,
// 然后第二个线程开始执行,第二个线程还是会判断为null,抢占到锁之后进行new对象操作,这样就保证不了单例。
public class Singleton3 {
    //1、私有化构造方法,让外部不能new出对象
    private void singleton3(){};
    //2、类加载时,初始化为null
    private static Singleton3 singleton3=null;
    //3、公共方法
    public static Singleton3 getSingleton(){
        //3、如果对象为null,就加锁初始化对象
        if (singleton3==null){
            synchronized (Singleton3.class){
                singleton3=new Singleton3();
            }
        }
        return singleton3;
    }
}

4、双重加锁

(1)、思想

在懒汉式加锁的基础上,再加一层锁。先判断null,如果对象为null就加锁,
在加锁代码里面第二次判断对象是否为null,如果为null再进行new对象,
这样就可以保证多线程下的单例。
因为就算因为线程挂起的原因,多个线程进入了第一层为null判断,
这个时候通过synchronized可以保证只有一个线程进入加锁逻辑,
等这个线程执行完成释放锁之后,
其它线程就算获取锁进入之后也通不过第二次的if为null判断。

缺点在于代码逻辑复杂

(2)、步骤

 1. 私有化构造方法 声明static修饰的成员变量,先不初始化
 2. 声明公有方法,在方法里面先第一次判断static成员变量是否为null,
    如果不为null则直接返回。
 3. 如果为null则加锁,在加锁逻辑里面第二次判断变量是否为null,
 	如果还是为null则进行new操作。

(3)、代码

//双重检查,第一个if判断,如果为null,就加锁处理
//在synchronized里面再进行第二次if判断,这样就可以防止第一次判断完成之后,
//还未加锁之前,其它线程进入的情况,导致的不能保证单例。
//第一次if判断也很有必要,因为大多少情况下,并不需要执行到加锁代码,就可以直接返回。
public class Singleton4 {

    //1、私有化构造方法,让外部不能new出对象
    private void singleton4(){};
    //2、类加载时,初始化为null
    private static Singleton4 singleton4=null;

    //3、公共方法
    public static Singleton4 getSingleton(){
        //3、如果对象为null,就加锁初始化对象
        if (singleton4==null){
            synchronized (Singleton4.class){
                if (singleton4==null){
                    singleton4=new Singleton4();
                }
            }
        }
        return singleton4;
    }
}

5、静态内部类实现

(1)、思想

利用静态内部类,在外部类被加载的时候不会加载内部类的特性,实现懒加载。
利用静态内部类之后被加载一次,内部类里面的成员变量只会被初始化一次的特性,
实现线程安全。

缺点在于会生成一个内部类。

(2)、步骤

 1. 私有化构造方法 。
 2. 声明一个静态内部类,内部类里面声明一个静态成员变量,new对象指向这个变量。
 3. 声明一个公有方法,方法里面返回静态内部类里面的静态成员变量。

(3)、代码

//利用静态内部类在外部类加载的时候,不会初始化的特性,保证懒加载
// 通过公有方法,返回内部类的静态变量,在加载静态内部类的时候可以保证单例。
public class Singleton5 {

    //1、私有化构造方法,让外部不能new出对象
    private void singleton5(){};
    //2、声明静态内部类,在内部类里面实例化对象
    private static class  innerSingleton {
        private static Singleton5 singleton5=new Singleton5();
    }

    //3、公共方法,返回内部类的静态变量
    public static Singleton5 getSingleton(){
        return innerSingleton.singleton5;
    }

}

6、枚举方式实现

(1)、思想

因为枚举类编译之后,实际上是一个继承了Enum<?>接口的常量类,
每个枚举值编译之后实际上是一个静态常量类对象。
所以根据静态常量类这些特性,就可以保证单例和线程安全。
并且枚举编译之后,是没有构造方法的,这就防止了外部利用反射来生成这个类对象破坏单例
而且在实现对象序列化的时候,也对枚举类做了限制,会抛出异常,
所以也防止了外部通过序列化方式来破坏单例。

缺点在于这种方式虽然是最完美的,但是违反常识,不便理解。

(2)、步骤

 1. 声明一个枚举类 
 2. 声明一个枚举值 
 3. 在客户端调用这个枚举值获取单例对象。

(3)、代码

//通过枚举方式,枚举类编译之后是常量类 public final Singleton6 extends Enum<Singleton6>
//枚举值编译之后是常量public static final Singleton6 INSTANCE
//所以可以保证单例和线程安全,并且因为枚举类没有构造方法,
//还可以防止调过反射生成对象,有校验机制,可以防止序列化。
public enum Singleton6 {
    INSTANCE;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值