前言
单例模式(Singleton Pattern)保证一个类仅有一个实例,并提供一个它的全局访问点。
一.单例模式简介
单例模式(Singleton Pattern)为了防止多次实例化一个对象,为该对象提供一个访问该实例的方法,让类自身负责保存它唯一的实例,这个类可以保证没有其他实例可以被创建。
二.单例模式多种用法分析
1.懒汉式–非线程安全单例模式
设计分析:需要一个私有的(private
)空构造方法,防止外部类通过new创建实例。提供一个创建实例的方法getinstance()
来创建实例,若实例已经被创建过,则直接返回原有实例。
线程安全问题:在单线程的程序中,用此方法可以实现单例,但在多线程编程中,当两个线程同时调用getinstance()
方法时,因为两个线程的instance
对象都为空,这两个线程可能都会执行new Singleton()
,这两个线程可能各自创建一个instance
对象,创建出多个instance
对象即不是单例模式了,也就存在线程安全的问题了。
为什么称为懒汉:要在第一次引用实例的时候,才进行实例化,实现懒加载效果,节省内存资源,所以称为懒汉式。
代码如下:
package com.pattern.singleton.singleton;
/**
* 懒汉式-非线程安全式
*
* @author 葫芦娃
*
*/
public class Singleton {
// 单例对象
private static Singleton instance;
// 私有的构造函数,使用private修饰,防止外界通过new再次创建对象
private Singleton() {
}
// 提供一个外部访问该实例的方法
public static Singleton getinstance() {
// 只有当不存在的时候创建(线程不安全,没有加锁,可以被多个线程调用)
if (instance == null) {
instance = new Singleton();
}
// 若存在,直接返回该实例
return instance;
}
}
2.懒汉式——线程安全单例模式
设计分析:与上面的不同之处在于,getinstance()
方法添加了synchronized
关键字修饰,即添加了方法同步,多线程会在进入此方法前排队依次执行,所以就解决了上面方式的线程安全问题,多线程编程中使用,也可以保证对象实现单例。
代码如下:
package com.pattern.singleton.singleton;
/**
* 懒汉式-线程安全式
* @author 葫芦娃
*
*/
public class LazySingleton {
// 单例对象
private static LazySingleton instance;
// 私有的构造函数,使用private修饰,防止外界通过new再次创建对象
private LazySingleton() {
}
// 提供一个外部访问该实例的方法,并使用synchronized关键字同步
public static synchronized LazySingleton getinstance() {
// 只有当不存在的时候创建
if (instance == null) {
instance = new LazySingleton();
}
// 若存在,直接返回该实例
return instance;
}
}
3.饿汉式–线程安全单例模式
设计分析: 在类初始化静态变量的时候,将instance
实例化,提供私有的构造方法,防止通过new
创建对象,提供getinstance()
方法来创建实例。
线程安全问题: 因为类加载的时候,实例就已经初始化完成,就算有多个线程调用,也不会重复创建了,但缺点就是浪费内存资源。
为什么称为饿汉:在类加载的时候,初始化静态变量的时候就创建了该对象。就像一个饿汉,不管有用没用的,先吃到肚子里再说。
代码如下:
package com.pattern.singleton.singleton;
/**
* 饿汉式单例模式(线程安全)
*
* @author 葫芦娃
*
*/
public class HungrySingleton {
// 静态单例对象,程序运行直接初始化
private static HungrySingleton instance =new HungrySingleton();
// 私有的构造函数,使用private修饰,防止外界通过new再次创建对象
private HungrySingleton() {
}
// 提供一个外部访问该实例的方法
public static HungrySingleton getinstance() {
//在静态变量中已经初始化,直接返回该实例
return instance;
}
}
4.双检锁/双重校验锁式——线程安全单例模式
设计分析: 提供一种即不耗费内存资源(懒加载),还能保证线程安全的方式创建单例模式。
双重校验:
1. 第一重校验:if (instance == null)
防止实例已经初始化,还要让多线程每次进行synchronized
排队,提高效率。
2. 第二重校验:if (instance == null)
防止多线程并发时,保证只有一个线程在进行实例化操作,即实现单例,还能保证线程安全。
代码如下:
package com.pattern.singleton.singleton;
/**
* 双检锁/双重校验锁式的单例(线程安全)
*
* @author 葫芦娃
*
*/
public class DoubleCheckLockingSingleton {
// volatile修饰的单例对象
private static volatile DoubleCheckLockingSingleton instance;
// 私有的构造函数,使用private修饰,防止外界通过new再次创建对象
private DoubleCheckLockingSingleton() {
}
// 提供一个外部访问该实例的方法
public static DoubleCheckLockingSingleton getinstance() {
//第一重校验:防止实例已经初始化,还要每次进行加锁,提高效率
if (instance == null) {
// 线程同步,当多个线程的instance为null,都可以通过第一重校验
synchronized (DoubleCheckLockingSingleton.class) {
// //第二重校验:防止多个线程的访问,只允许有一个进入,其他线程排队等第一个线程访问结束再进入,保证instance对象的单例
if (instance == null) {
instance = new DoubleCheckLockingSingleton();
}
}
}
return instance;
}
}
5.客户端:
在客户端中,对比实例是单例,即创建的都是同一个实例。
代码如下:
package com.pattern.singleton.client;
import com.pattern.singleton.singleton.DoubleCheckLockingSingleton;
import com.pattern.singleton.singleton.HungrySingleton;
import com.pattern.singleton.singleton.LazySingleton;
import com.pattern.singleton.singleton.Singleton;
public class Client {
public static void main(String[] args) {
// 通过new Singleton()来创建实例已经被禁用了,只能通过下面提供的方式
Singleton singleton1 = Singleton.getinstance();
Singleton singleton2 = Singleton.getinstance();
if (singleton1 == singleton2) {
System.out.println("懒汉式非线程安全:singleton1和singleton2是相同实例");
} else {
System.out.println("懒汉式非线程安全:singleton1和singleton2是不同实例");
}
// 线程安全的单单例模式
final LazySingleton lazySingleton4 = LazySingleton.getinstance();
// 饿汉式单例,保证了线程安全
final HungrySingleton hungrySingleton5 = HungrySingleton.getinstance();
// 双重校验创建线程安全的单例对象
final DoubleCheckLockingSingleton doubleCheckLockingSingleton7 = DoubleCheckLockingSingleton.getinstance();
new Thread(new Runnable() {
public void run() {
LazySingleton lazySingleton5 = LazySingleton.getinstance();
if (lazySingleton4 == lazySingleton5) {
System.out.println("懒汉式线程安全:lazySingleton4和lazySingleton4是相同实例");
} else {
System.out.println("懒汉式线程安全:lazySingleton4和lazySingleton4是不同实例");
}
HungrySingleton hungrySingleton6 = HungrySingleton.getinstance();
if (hungrySingleton5 == hungrySingleton6) {
System.out.println("饿汉式线程安全:hungrySingleton5和hungrySingleton6是相同实例");
} else {
System.out.println("饿汉式线程安全:hungrySingleton5和hungrySingleton6是不同实例");
}
DoubleCheckLockingSingleton doubleCheckLockingSingleton8 = DoubleCheckLockingSingleton.getinstance();
if (doubleCheckLockingSingleton7 == doubleCheckLockingSingleton8) {
System.out.println("双重校验锁式线程安全:doubleCheckLockingSingleton7和doubleCheckLockingSingleton8是相同实例");
} else {
System.out.println("双重校验锁式线程安全:doubleCheckLockingSingleton7和doubleCheckLockingSingleton8是不同实例");
}
}
}).start();
}
}
6.运行结果展示
懒汉式非线程安全:singleton1和singleton2是相同实例
懒汉式线程安全:lazySingleton4和lazySingleton4是相同实例
饿汉式线程安全:hungrySingleton5和hungrySingleton6是相同实例
双重校验锁式线程安全:doubleCheckLockingSingleton7和doubleCheckLockingSingleton8是相同实例
7.源码下载
本文示例代码下载地址:点击下载
三.总结:
1.单例模式的优点:
- 只有一个实例,减少内存的开销。
- 对资源不会多重的占用,比如避免了多个实例对一份问价读写。
2.单例模式的缺点:
- 与
单一职责
原则冲突,一个类应该只关注它本身,不应该关注外面的类如何实例化它。 - 不能提供接口。
- 不能够继承。