单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点。
我们常常希望某个对象实例只有一个,不想要频繁地创建和销毁对象,浪费系统资源,最常见的就是 IO 、数据库的连接、Redis 连接等对象,完全没有必要创建多个一模一样的对象,一个足矣。
实现思路:(俗称"两私一公开")
- 私有静态实例(懒实例化/直接实例化)
- 私有构造方法
- 公开的静态获取方法
要点:
- 使用加锁或者静态变量、静态内部类、枚举等手段提供线程安全的保证
- 突破线程安全的手段:反射
典型实现
- Hungry Singleton
- Lazy Singleton
- Double Checked Locking Singleton
- Static Class Singleton
- Enum Singleton
使用场景
- 需要频繁地创建和销毁,从而浪费系统资源的对象,例如:IO 、数据库的连接、Redis。使用单例模式能够有效地解决这一问题。
实例
SimpleSingleton
/**
* 非线程安全的简单的单例模式
*/
@NotThreadSafe
public class SimpleSingleton {
private static SimpleSingleton instance;
private SimpleSingleton(){}
//当多个线程同时调用 getInstance() 方法时,可能会产生多个 instance 实例,因此这种方式并不是真正的单例。
public static SimpleSingleton getInstance(){
if(instance == null){
instance = new SimpleSingleton();
}
return instance;
}
}
LazySingleton
/**
* 懒汉式的单例模式:只在首次调用获得对象实例的方法时才会实例化对象
*
*
* 优点:
* 1. 实现了延迟加载
*
* 缺点:
* 1. 使用延迟加载而引入同步关键字synchronized反而降低了系统的性能
*/
@ThreadSafe
public class LazySingleton {
/**
* 私有化的构造函数,确保单例不会再系统中的其他地方被实例化
*/
private LazySingleton() {
System.out.println("LazySingleton is creating");
}
/**
* instance初始值被赋值null,确保系统启动时没有额外的负载
*/
private static LazySingleton instance = null;
/**
* 首先判断当前单例是否已经存在,若存在则直接返回,不存在则新建后再返回新建的
* synchronized关键字保证了多线程环境下instance == null判断的安全性
*
* @return
*/
public static synchronized LazySingleton getInstance() {
if (instance == null) {
instance = new LazySingleton();
}
return instance;
}
}
HungrySingleton
/**
* 饿汉式单例模式:在单例类被加载时候,就实例化一个对象交给自身管理的引用
*
* 优点:简单,可靠。
*
* 缺点:无法对instance实例做延迟加载。具体而言
* 首次,在首次使用到该单例类时会进行类加载,该加载的过程可能会很慢,从而影响到应用的启动速度;
* 其次,该单例类在系统中还扮演者其他角色,那么任何使用到该单例类的地方都会初始化类的实例变量,而不管是否会用到。
*
*/
@ThreadSafe
public class HungrySingleton {
/**
* 私有化的构造函数,确保单例不会再系统中的其他地方被实例化
*/
private HungrySingleton() {
System.out.println("HungrySingleton is created");
}
/**
* private保证无法直接被访问,static保证只有一份
*/
private static HungrySingleton instance = new HungrySingleton();
/**
* static允许通过类直接获取,public表明这是一个公共的允许访问的入口
* @return
*/
public static HungrySingleton getInstance() {
return instance;
}
/**
* 创建字符串的工具方法,模拟该单例扮演的其他角色
* @return
*/
public static String createString(){
System.out.println("createString in HungrySingleton");
return "demo string";
}
}
DoubleCheckedLockingSingleton
/**
* double-checked locking( 双重检查加锁 )
* 这种方式主要用到两个关键字 volatile 和 synchronized
*/
@ThreadSafe
public class DoubleCheckedLockingSingleton {
private volatile static DoubleCheckedLockingSingleton instance;
private DoubleCheckedLockingSingleton() {
}
public static DoubleCheckedLockingSingleton getInstance() {
// 第一次判空检查,volatile保证了可见性,多个线程看到的是一致的结果
// 如果判断为非空,多个线程拿到同一个对象也是OK的,所以第一次判断时无需加锁
if (instance == null) {
synchronized (DoubleCheckedLockingSingleton.class) {
// 由于需要新建对象,因此需要加锁实现同步
if (instance == null) {
instance = new DoubleCheckedLockingSingleton();
}
}
}
return instance;
}
}
StaticClassSingleton
/**
* 懒汉式的单例模式,采用内部类实现,可以实现延迟加载
*
* 实例的建立是在类加载时完成,故天生对多线程友好,无需使用synchronized关键字进行同步。
* 即实现了延迟加载,也避免了同步锁的性能开销,正所谓一举两得。
*/
@ThreadSafe
public class StaticClassSingleton {
/**
* 私有化的构造函数,确保单例不会再系统中的其他地方被实例化
*/
private StaticClassSingleton() {
System.out.println("LazySingleton is creating");
}
/**
* 使用内部类来维护单例的实例,当StaticSingleton被加载时,其内部类并不会被初始化,故可以确保党
* StaticSingleton被JVM载入时,不会初始化instance。
*/
private static class SingletonHolder {
private static StaticClassSingleton instance = new StaticClassSingleton();
}
/**
* 当getInstance()方法被调用时,才会加载SingletonHolder,从而初始化instance。
* @return
*/
public static StaticClassSingleton getInstance() {
return SingletonHolder.instance;
}
}
EnumSingleton
/**
* 使用枚举实现的单例模式
*/
@ThreadSafe
public class EnumSingleton{
// 私有的构造方法
private EnumSingleton(){}
// 内部枚举类
private static enum Singleton{
INSTANCE;
private EnumSingleton singleton;
//JVM会保证此方法绝对只调用一次
private Singleton(){
singleton = new EnumSingleton();
}
public EnumSingleton getInstance(){
return singleton;
}
}
// 公开的全局访问点
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
}
}
单元测试
/**
* 单例模式的测试类
*/
public class SingletonTest extends BaseTest {
private static final Integer LIMIT = 1000;
@Test
public void hungrySingletonTest() {
long beginTime = System.currentTimeMillis();
for (int i = 0; i < LIMIT; ++i) {
HungrySingleton.getInstance();
}
System.out.println("hungrySingleton:" + (System.currentTimeMillis() - beginTime));
}
@Test
public void lazySingletonTest() {
long beginTime = System.currentTimeMillis();
for (int i = 0; i < LIMIT; ++i) {
LazySingleton.getInstance();
}
System.out.println("lazySingleton:" + (System.currentTimeMillis() - beginTime));
}
@Test
public void enumSingletonTest() {
EnumSingleton obj1 = EnumSingleton.getInstance();
EnumSingleton obj2 = EnumSingleton.getInstance();
//输出结果:obj1==obj2?true
System.out.println("obj1==obj2?" + (obj1 == obj2));
}
}
网友评论