Java设计模式——单例模式

简介
所谓单例模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得对象实例的方法(静态方法)

单例模式注意事项和使用说明

  • 单例模式保证了系统内存中只存在一个对象,节省了系统资源,对一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能

  • 实例化一个单例类时,要调用获取对象的方法而不是new

  • 单例模式使用场景:需要频繁创建和销毁的对象、创建对象时耗时过多或者耗费资源过多,但又经常用到的对象、工具类对象、频繁访问数据库或者文件的对象

单例模式的几种方法

  1. 饿汉式

  2. 懒汉式

  3. 双重检查

  4. 静态内部类

  5. 枚举

饿汉式

饿汉式细分又可以分为两种,一种是静态常量,一种是静态代码块

饿汉式(静态常量)

实现步骤

  1. 构造器私有化(防止new)

  2. 类的内部构造对象

  3. 向外暴露一个静态的公共方法获取对象实例

public class SingletonTest01 {
    public static void main(String[] args) {
    	// 测试
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
        System.out.println("====================");
        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
    }
}

// 饿汉式(静态变量)
class Singleton {
    // 1、构造器私有化,外部不能new
    private Singleton() {
    }

    // 2、本类内部创建对象实例
    private final static Singleton instance = new Singleton();

    // 3、提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance() {
        return instance;
    }
}
// 运行结果
true
====================
1735600054
1735600054

// 说明了这两个对象为同一个实例

优缺点:

  • 优点:写法简单,在类加载时完成实例化,避免了线程同步问题

  • 缺点:在类装载时完成实例化,没有达到懒加载的效果(Lazy Loading),如果程序从头到尾都没有使用这个实例,则会造成内存的浪费

结论:这种单例模式可用,但可能会造成内存浪费

饿汉式(静态代码块)

这种方式和静态常量方法非常相似,只不过是把单例初始化放到了静态代码块中,其他都不需要改变

public class SingletonTest {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
        System.out.println("====================");
        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
    }
}

// 饿汉式(静态代码块)
class Singleton {
    // 1、构造器私有化,外部不能new
    private Singleton() {
    }

    // 2、本类内部创建对象实例
    private static Singleton instance;

    static {    // 在静态代码块中创建单例对象
        instance = new Singleton();
    }

    // 3、提供一个公有的静态方法,返回实例对象
    public static Singleton getInstance() {
        return instance;
    }
}
// 运行结果
true
====================
1735600054
1735600054

// 说明了这两个对象为同一个实例

优缺点与结论
与饿汉式(静态常量)相同

懒汉式

懒汉式实现了懒加载,减少了内存空间的浪费

懒汉式(线程不安全)
public class SingleTonTest {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
        System.out.println("====================");
        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
    }
}

class Singleton {

    private static Singleton instance;

    // 构造器私有化
    private Singleton() {
    }

    // 提供一个静态的公有方法,当使用到该方法时,才去创建instance
    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}
// 运行结果
true
====================
1735600054
1735600054

// 说明了这两个对象为同一个实例

优缺点

  • 优点:起到了懒加载的效果,但是只能在单线程下使用
  • 缺点:如果在多线程下,一个线程进入了if (instance == null)判断语句,还未来得及往下执行,另一个线程也通过了这个判断语句,这时就会产生多个实例,就不是单例模式了,所以在多线程下可能会有多个实例产生

结论:实际开发中,不要使用这种方式

懒汉式(线程安全,同步方法)

实现这种模式非常简单,只需要在线程不安全的获取实例方法代码中加上synchronized即可
在这里插入图片描述
这样线程就会排队执行getInstance方法,从而实现只有一个实例

优缺点:

  • 优点:解决了线程不安全问题

  • 缺点:效率太低,想要获取类的实例时,每次执行getInstance方法都需要进行同步

  • 使用设计模式的目的则是提高程序效率,实际开发中,会经常调用getInstance方法,使得效率低下,这与我们的目的相悖

结论:实际开发中,不推荐使用

懒汉式(同步代码块)

有人想用这种方法来改进 懒汉式(线程安全,同步方法)实现提高效率,但这种方法其实是一种错误方法,不仅做不到提高效率,而且也无法实现线程安全

// 该方法的核心部分
public static Singleton getInstance() {
        if (instance == null) {
        	synchronized (Singleton.class){
        		instance = new Singleton();
			}  
        }
        return instance;
    }

把 synchronized 加到代码块上并不会提高效率,而且外层缺少了 synchronized 会造成有多个线程进入了if语句中,而进入了if语句就会创建一个实例,只不过现在是排队创建罢了

结论:白给写法,千万不要写

双重检查(推荐)

该模式不仅解决了线程安全问题,还是实现了懒加载,提高了效率

public class SingletonTest {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
        System.out.println("====================");
        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
    }
}

class Singleton {

    // volatile:将修改值立即更新到主存,防止重排序,相较synchronized来说是一个轻量级的,只能修饰变量
    private static volatile Singleton instance;
	
	// 构造器私有化
    private Singleton() {
    }

    // 提供一个公有的静态方法,加入双重检查代码,保证了只会有一个线程执行,解决线程安全问题和懒加载问题
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
// 运行结果
true
====================
1735600054
1735600054

// 说明了这两个对象为同一个实例

说明

  • 双重检查(Double-Check)是多线程开发中经常使用的,使用两次if (instance == null)检查保证线程安全

  • 实例化代码只执行了一次,后面再访问时直接return实例化对象,避免了反复的方法同步(方法同步效率很低)

  • 实现了懒加载,效率较高

结论:实际开发中,推荐使用这种单例设计模式

静态内部类

当类被装载时,静态内部类不会被装载,当我们使用静态变量时,静态内部类才会被装载且只装载一次,而且类的装载过程中线程是安全的

public class SingletonTest {
    public static void main(String[] args) {
        Singleton instance = Singleton.getInstance();
        Singleton instance2 = Singleton.getInstance();
        System.out.println(instance == instance2);
        System.out.println("====================");
        System.out.println(instance.hashCode());
        System.out.println(instance2.hashCode());
    }
}

class Singleton {

    // 构造器私有化
    private Singleton() {
    }

    // 写一个静态内部类,该类中有一个静态属性
    private static class SingletonInstance {
        private static final Singleton INSTANCE = new Singleton();
    }

    // 提供一个公有的静态方法,直接返回静态内部类的成员变量
    public static Singleton getInstance() {
        return SingletonInstance.INSTANCE;
    }
}
// 运行结果
true
====================
1735600054
1735600054

// 说明了这两个对象为同一个实例

说明

  • 这种方式采用了类加载的机制保证初始化实例时只有一个线程

  • SingletonInstance类不会立即被装载,只有调用getInstance方法后才会装载SingletonInstance类,从而完成Singleton的实例化

  • 类的静态属性只会在第一加载类的时候初始化

  • 避免了线程不安全,利用静态内部类的特点实现懒加载,效率高

结论:推荐使用

枚举

public class SingletonTest {
    public static void main(String[] args) {
        Singleton instance1 = Singleton.INSTANCE;
        Singleton instance2 = Singleton.INSTANCE;
        System.out.println(instance1 == instance2);
        System.out.println("====================");
        System.out.println(instance1.hashCode());
        System.out.println(instance2.hashCode());
    }
}

enum Singleton {
    INSTANCE;   // 属性
    public void sayOK() {
        System.out.println("OK");
    }
}

说明

  • 借助JDK1.5中添加的枚举拉实现单例模式,不仅可以避免多线程同步问题,而且还能防止反序列化重建创建新的对象

结论:推荐使用

题外话:其实单例模式在我刚开始学Java的时候就接触到了,当时学线程的时候什么懒汉饿汉,双重检查,死锁什么的差点把我送走,但现在重新再看这个的话就感觉非常简单了,看来我也是有进步得嘛

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值