文章目录
前言
数学与逻辑学中,singleton定义为有且仅有一个元素的集合。
单例模式最初的定义出现于 《设计模式》(艾迪生维斯理, 1994:“保证一个类仅有一个实例,并提供一个访问它的全局访问点。”
一、什么是单例模式
单例模式指的是一个类,在全局范围内(整个系统中)有且只能有一个实例存在。即该类本身负责提供一种访问其唯一对象的方式,可以直接访问,不需要实例化该类的对象。单例模式也不一定只有一个实例,也可以通过暴力的反射,对象序列化等方式去创建多个对象(枚举除外)
二、单例模式的类型有哪些
单例模式有两种类型:
- 饿汉式:在类加载的时候就已经创建好该类的对象了,不会存在并发安全和性能问题。
- 懒汉式:在真正需要使用对象时才去创建该单例类对象
三、单例模式的实现方式
-
饿汉式
它基于 classloader 机制避免了多线程的同步问题,instance 在类装载时就实例化,在程序调用时直接返回该单例对象即可-
优点:对象提前创建好了,没有加锁,执行效率会提高。线程安全
-
缺点:类加载时就初始化,产生垃圾对象,浪费内存。
-
/**
* 饿汉式实例
* */
public class Singleton {
//构造方法私有
private Singleton(){}
//内部私有静态的创建对象
private final static Singleton s = new Singleton();
//对外提供一个静态方法实例化对象
public static Singleton getInstance(){
return s;
}
}
它在类加载的时候就在内部实例化对象但是不创建对象,调用静态方法的时候才会创建对象,这样就会存在线程不安全问题
-
优点:使用对象时,对象才创建,所以不会提前占用内存,内存占用小。
-
缺点:这种方式 lazy loading 很明显,
线程不安全
,在多线程不能正常工作。
/**
* 懒汉式实例
* */
public class Singleton {
//构造方法私有
private Singleton (){}
//内部私有静态的实例化对象但是不创建
private static Singleton instance;
//对外提供一个静态方法实例化对象
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
这就是加上synchronize之类保证线程安全的基础上的懒汉模式,这种方式具备很好的 lazy loading,能够在多线程中很好的工作,但是,效率很低,99% 情况下不需要同步。
-
优点:第一次调用才初始化,避免内存浪费。
-
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。是牺牲了性能来换取安全。
/**
* 懒汉式(synchronize)
* */
public class Singleton {
//构造方法私有
private Singleton(){}
//内部私有静态的实例化对象但是不创建
private static Singleton s;
//外部获取该类对象的方法(线程安全的)加synchronized
public synchronized static Singleton getInstance(){
if ( s== null){
s = new Singleton();
}
return s;
}
}
在懒汉式基础上利用synchronize关键字和volatile关键字确保第一次创建时没有线程间竞争而产生多个实例,仅第一次创建时同步,性能相对懒汉式(synchronize)较高
-
优点:安全且在多线程情况下能保持高性能,
-
缺点:实现难度复杂。
这里对比加synchronize 和双重校验锁的代码来讲解双重校验锁
/**
* 懒汉式(synchronize)
* */
public class Singleton {
//构造方法私有
private Singleton(){}
//内部私有静态的实例化对象但是不创建
private static Singleton s;
//外部获取该类对象的方法(线程安全的)加synchronized
//这里是粗暴地对整个 getInstance() 方法加锁,导致每次调用 getInstance()都会加锁。
//只有当第一次调用 getInstance() 时才需要同步创建对象,创建之后再次调用 getInstance() 时就只是简单的返回成员变量,
//而这里是无需同步的, 所以没必要对整个方法加锁。
public synchronized static Singleton getInstance(){
if ( s== null){
s = new Singleton();
}
return s;
}
}
/**
* 懒汉式(synchronize)
* */
public class Singleton {
//构造方法私有
private Singleton(){}
//内部私有静态的实例化对象但是不创建
private static Singleton s;
public static Singleton getInstance(){
synchronized(Singleton.class) { // 加 synchronized
if (s== null) {
s= new Singleton ();
}
}
return s;
}
}
//按照上述的代码还会出现一个问题禁止指令重排,对于这个问题在这就不详细讲述了,对于这个问题就用volatile关键字来解决
/**
* synchronize volatile 双检锁/双重校验锁
* */
public class Singleton {
//构造方法私有
private Singleton () {}
private volatile static Singleton s; // 加 volatile
public static Singleton getInstance() {
synchronized(Singleton.class) { // 加 synchronized
if (s== null) {
s= new Lock2Singleton();
}
}
return s;
}
}
这种方式使用静态内部类,因为静态内部类只会在类被调用时候加载一次,后面就不会加载,所以可以保证线程是安全的,而且也替代了锁机制,减少了资源浪费。。
-
优点:对比双检锁/双重校验锁方式实现跟简单。线程安全,不占内存,延迟实例化对象。
-
缺点:静态内部类也有着一个致命的缺点,就是传参的问题,由于是静态内部类的形式去创建单例的,故外部无法传递参数进去。
/**
* 登记式/静态内部类
* */
public class Singleton {
//构造方法私有
private Singleton(){}
//静态内部类
private static class inner{
private static final Singleton s = new Singleton2();
}
//静态实例化对象方法
public static Singleton getInstance(){
return inner.s;
}
}
这是实现单例模式的最佳方法。它更简洁,它不仅能避免多线程同步问题,而且还自动支持序列化机制,防止反序列化重新创建新的对象,绝对防止多次实例化。在日出开发中也是经常用来定义一组常量
-
优点:代码简洁,不需要其他操作去保持线程安全,他与上面几种方式最大的优势是他绝对单例,枚举可以防止调用者使用反射、序列 化与反序列化机制强制生成多个单例对象,破坏单例模式。
-
缺点:静态内部类也有着一个致命的缺点,就是传参的问题,由于是静态内部类的形式去创建单例的,故外部无法传递参数进去。
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
总结
- 单例模式是指在全局中只有一个实例
- 单例模式常见的为饿汉式和懒汉式
- 在日常开发中建议不要使用普通懒汉式和懒汉式(synchronize)
- 枚举是唯一一种单例模式可以防止反射和序列化和反序列化的单例模式