简介
所谓单例模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得对象实例的方法(静态方法)
单例模式注意事项和使用说明
-
单例模式保证了系统内存中只存在一个对象,节省了系统资源,对一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
-
实例化一个单例类时,要调用获取对象的方法而不是new
-
单例模式使用场景:需要频繁创建和销毁的对象、创建对象时耗时过多或者耗费资源过多,但又经常用到的对象、工具类对象、频繁访问数据库或者文件的对象
单例模式的几种方法
-
饿汉式
-
懒汉式
-
双重检查
-
静态内部类
-
枚举
饿汉式
饿汉式细分又可以分为两种,一种是静态常量,一种是静态代码块
饿汉式(静态常量)
实现步骤
-
构造器私有化(防止new)
-
类的内部构造对象
-
向外暴露一个静态的公共方法获取对象实例
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的时候就接触到了,当时学线程的时候什么懒汉饿汉,双重检查,死锁什么的差点把我送走,但现在重新再看这个的话就感觉非常简单了,看来我也是有进步得嘛