一、单例模式简介
单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
注意:
- 1、单例类只能有一个实例。
- 2、单例类必须自己创建自己的唯一实例。
- 3、单例类必须给所有其他对象提供这一实例。
介绍
意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
主要解决:一个全局使用的类频繁地创建与销毁。
何时使用:当您想控制实例数目,节省系统资源的时候。
如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。
关键代码:构造函数是私有的。
优点:
- 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
- 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
使用场景:
- 1、要求生产唯一序列号。
- 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
- 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
二、单例模式的实现
单例设计模式分两种:
饿汉式:类加载就会导致该单实例对象被创建
懒汉式:类加载不会导致该单实例对象被创建,而是首次使用该对象时才会被创建
三、代码实现
1、饿汉式:静态成员变量
package SP_01_Hungry;
/**
* @Author: {LZG}
* @ClassName: SP_01_Hungry.Singleton
* @Description: 饿汉式:静态成员变量
* @Date: 2022/3/31 14:06
**/
public class Singleton {
// 1、私有构造法方法
private Singleton() {
}
// 2、在本类中创建本类对象
private static Singleton instance=new Singleton();
// 3、提供一个公共的方式访问,让外界获取该对象
public static Singleton getInstance(){
return instance;
}
}
测试
package SP_01_Hungry;
/**
* @Author: {LZG}
* @ClassName: Client
* @Description: TODO
* @Date: 2022/3/31 14:10
**/
public class Client {
public static void main(String[] args) {
// 创建Singleton类的对象
Singleton instance1=Singleton.getInstance();
Singleton instance2=Singleton.getInstance();
// 判断获取两到的两个是否是同一对象
System.out.println(instance1==instance2);
}
}
执行结果
2、饿汉式:静态代码块
package SP_02_Hungry2;
/**
* @Author: {LZG}
* @ClassName: Singleton
* @Description: 饿汉式:静态代码块
* @Date: 2022/3/31 14:32
**/
public class Singleton {
// 私有构造方法
private Singleton(){}
// 声明Singleton类型的变量
private static Singleton instance; // null
// 在静态代码块赋值
static{
instance=new Singleton();
}
// 对外提供该类对象的获取方法
public static Singleton getInstance(){
return instance;
}
}
测试
package SP_02_Hungry2;
/**
* @Author: {LZG}
* @ClassName: Client
* @Description: TODO
* @Date: 2022/3/31 14:10
**/
public class Client {
public static void main(String[] args) {
// 创建Singleton类的对象
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
// 判断获取两到的两个是否是同一对象
System.out.println(instance1==instance2);
}
}
运行结果
3、懒汉式:线程不安全
package SP_03_Lazy;
/**
* @Author: {LZG}
* @ClassName: Singleton
* @Description: 懒汉式:线程不安全
* @Date: 2022/3/31 14:48
**/
public class Singleton {
// 私有构造方法
private Singleton(){}
// 声明Singleton类型的变量instance
private static Singleton instance; // 只是声明一个该类型的变量,并没有进行赋值
// 对外提供访问方式
public static Singleton getInstance(){
if(instance==null){
// 线程1等待,线程2获取到了cpu的执行权,也会进入到判断里边
instance=new Singleton();
}
return instance;
}
}
测试
package SP_03_Lazy;
/**
* @Author: {LZG}
* @ClassName: Client
* @Description: TODO
* @Date: 2022/3/31 14:50
**/
public class Client {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
}
}
测试结果
4、懒汉式:线程安全
package SP_04_Lazy;
/**
* @Author: {LZG}
* @ClassName: Singleton
* @Description: 懒汉式:线程安全
* @Date: 2022/3/31 14:55
**/
public class Singleton {
// 私有构造方法
private Singleton(){}
// 声明Singleton类型的变量instance
private static Singleton instance; // 只是声明一个该类型的变量,并没有进行赋值
// 对外提供访问方式
// synchronized:同步锁 线程1等待 线程2获得cpu的执行权 但是线程1没有释放同步锁 所以线程2进不去
public static synchronized Singleton getInstance(){
if(instance==null){
// 线程1等待,线程2获取到了cpu的执行权,也会进入到判断里边
instance=new Singleton();
}
return instance;
}
}
测试
package SP_04_Lazy;
/**
* @Author: {LZG}
* @ClassName: Client
* @Description: TODO
* @Date: 2022/3/31 14:58
**/
public class Client {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
}
}
运行结果
5、懒汉式:双重检查锁
对于getInstance()方法来说,绝大部分的操作都是都操作,读操作是线程安全的,所以没必要让每个线程必须持有锁才能调用该方法,我们需要调整加锁机制。由此产生了新的模式:双重检查锁模式
package SP_05_Lazy;
/**
* @Author: {LZG}
* @ClassName: Singleton
* @Description: 懒汉式:双重检查锁
* @Date: 2022/3/31 15:05
**/
public class Singleton {
// 私有构造方法
private Singleton(){}
// 声明Singleton类型的变量instance
private static Singleton instance; // 只是声明一个该类型的变量,并没有进行赋值
// 对外提供访问方式
public static Singleton getInstance(){
// 第一次判断。如果instance的值不为null,不需要抢占锁,直接返回对象
if(instance==null){
synchronized (Singleton.class){
// 第二次判断
if (instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
上述代码解决了单例、性能、线程安全问题,看似完美无缺,其实存在在多线程的情况下可能会出现空指针问题,出现JVM在实例化对象的时候会进行优化和指令重排序操作。要解决上述空指针异常问题,只需要使用volatile关键字,volatile可以保证可见性和有序性。
package SP_05_Lazy;
/**
* @Author: {LZG}
* @ClassName: Singleton
* @Description: 懒汉式:双重检查锁
* @Date: 2022/3/31 15:05
**/
public class Singleton {
// 私有构造方法
private Singleton(){}
// 声明Singleton类型的变量instance
private static volatile Singleton instance; // 只是声明一个该类型的变量,并没有进行赋值
// 对外提供访问方式
public static Singleton getInstance(){
// 第一次判断。如果instance的值不为null,不需要抢占锁,直接返回对象
if(instance==null){
synchronized (Singleton.class){
// 第二次判断
if (instance==null){
instance=new Singleton();
}
}
}
return instance;
}
}
6、懒汉式:静态内部类
静态内部类单例模式中实例由内部类创建,由于JVM在加载外部类的过程中,是不会加载静态内部类,只由内部的属性/方法被调用的时候才会被加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序
package SP_06_Lazy;
/**
* @Author: {LZG}
* @ClassName: Singleton
* @Description: 懒汉式:静态内部类
* @Date: 2022/3/31 15:20
**/
public class Singleton {
// 私有构造方法
private Singleton(){}
// 定义一个静态内部类
private static class SingletonHolder{
private static final Singleton INSTANCE=new Singleton();
}
// 提供公共的访问方式
public static Singleton getInstance(){
return SingletonHolder.INSTANCE;
}
}
测试
package SP_06_Lazy;
/**
* @Author: {LZG}
* @ClassName: Client
* @Description: TODO
* @Date: 2022/3/31 15:25
**/
public class Client {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
}
}
运行结果
7、枚举方式
枚举实现单例模式是极力推荐的单例模式,因为枚举是线程安全的,并且置喙状再一次,枚举的写法十分简单,而且枚举类型是所用单例实现中唯一不会被破坏的单例实现模式
package SP_07_Enum;
/**
* @Author: {LZG}
* @ClassName: Singleton
* @Description: 枚举方式
* @Date: 2022/3/31 15:20
**/
public enum Singleton {
INSTANCE;
}
测试
package SP_07_Enum;
/**
* @Author: {LZG}
* @ClassName: Client
* @Description: TODO
* @Date: 2022/3/31 15:37
**/
public class Client {
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance1==instance2);
}
}
运行结果