一、概述
单例模式的意图:是为了确保一个类有且仅有一个实例,并为它提供一个全局访问点。
单例模式的要点有三个:
- 一是某个类只有一个实例,
- 二是它必须自行创建这个实例,
- 三是它必须自行向整个系统提供这个实例。
从实现角度来说,就是以下三点:
- 一是单例模式的类只提供私有的构造函数,
- 二是类定义中含有一个该类的静态私有对象,
- 三是该类提供了一个静态的公有函数用于创建或获取它本身的静态私有到对象。
二、Java实现单例模式的五种形式
1、饿汉式
/**
* 饿汉式:在类加载时就创建单例对象。
* 使用规范 —— 确定会用到一次时可以使用,不然可能造成内存浪费
*/
public class Singleton_ehan {
public static void main(String[] args) {
System.out.println("使用饿汉式单例模式");
getInstance();
}
private Singleton_ehan(){}
/*
* 饿汉式 —— 静态常量
* 优点:写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题
* 缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。
* 如果从始至终从未使用过这个实例,则会造成内存的浪费
*/
// private static Singleton_ehan instance = new Singleton_ehan();
/*
* 饿汉式 —— 静态代码块
* 优缺点同上
*/
private static Singleton_ehan instance;
static{
instance = new Singleton_ehan();
}
public static Singleton_ehan getInstance(){
return instance;
}
}
2、懒汉式(不推荐 / 不能 )
/**
* 懒汉式:真正用到时才去实例化单例对象
* 【不推荐使用】
*/
public class Singleton_lanhan {
public static void main(String[] args) {
System.out.println("懒汉式");
getInstance();
}
private Singleton_lanhan(){}
private static Singleton_lanhan instance;
/**
* 懒汉式——第一版
* 优点:起到了Lazy Loading的效果,但是只能在单线程下使用。
* 缺点:如果在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,
* 这时便会产生多个实例。所以在多线程环境下不可使用这种方式
*/
// public static Singleton_lanhan getInstance(){
// if(instance == null){
// instance = new Singleton_lanhan();
// }
// return instance;
// }
/**
* 懒汉式——第二版(同步方法)
* 优点:解决了线程不安全问题
* 缺点:效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。
* 其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。
* 方法进行同步效率太低
*/
// public static synchronized Singleton_lanhan getInstance(){
// if(instance == null){
// instance = new Singleton_lanhan();
// }
// return instance;
// }
/**
* 懒汉式——第三版(同步代码块)
* 优缺点同上(不能使用)
*/
public static Singleton_lanhan getInstance(){
if(instance == null){
synchronized(Singleton_lanhan.class) {
instance = new Singleton_lanhan();
}
}
return instance;
}
}
3、双重检查Double Check
/**
* Double Check:双重检查
* 优点:可以保证线程安全,延迟加载,效率较高
*【强烈推荐使用】
*/
public class Singleton_dc {
public static void main(String[] args) {
System.out.println("双重检查");
getInstance();
}
private Singleton_dc(){}
private static volatile Singleton_dc instance; //使用volatile关键字可以保证缓存一致性(内存可见性)
public static Singleton_dc getInstance(){
if(instance == null){
synchronized (Singleton_dc.class){
if(instance == null){
instance = new Singleton_dc();
}
}
}
return instance;
}
}
4、静态内部类
/**
* 静态内部类
* 分析:(1)类的静态属性只会在第一次加载类的时候初始化,所以在这里, JMM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入
* 采用了类装载的机制来保证初始化实例时只有一个线程
* (2)静态内部类方式在Singleton_static类被装载时并不会立即实例化 ——>
* 而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton_static的实例化。
* 优点:
* 避免了线程不安全,利用静态内部类特点实现延迟加载,效率高
* 【推荐使用】
*/
public class Singleton_static {
public static void main(String[] args) {
System.out.println("静态内部类");
getInstance();
}
private Singleton_static(){}
//定义静态内部类
private static class SingletonInstance{
private static final Singleton_static INSTANCE = new Singleton_static();
}
public static Singleton_static getInstance(){
return SingletonInstance.INSTANCE;
}
}
5、枚举
import org.omg.PortableInterceptor.INACTIVE;
/**
* 枚举
* 分析:借助JDK1.5中添加的枚举来实现单例模式。
* 优点:不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象。
* 【这种方式是Effective Java作者Josh Bloch 提倡的方式——强推】
*/
public class Singleton_enum {
public static void main(String[] args) {
Singleton instance1 = Singleton.INSTANCE;
Singleton instance2 = Singleton.INSTANCE;
System.out.println(instance1.equals(instance2)); //true
System.out.println(instance1.hashCode()); //1956725890
System.out.println(instance2.hashCode()); //1956725890
instance1.sayOK(); //enum ok!
}
}
/**
* Enum就是一个普通的类,它继承自java.lang.Enum类
*/
enum Singleton{
INSTANCE; //对象
public void sayOK(){
System.out.println("enum ok!");
}
}
三、单例模式在JDK 应用的源码分析
JDK中, java.lang.Runtime就是经典的单例模式 (饿汉式)
代码分析+Debug源码+代码说明(在代码块中任意地方输入Runtime,按住ctrl点击可调出其源码)
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}
四、单例模式注意事项和细节说明
1、单例模式保证了 系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象,使用单例模式可以提高系统性能
2、当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
3、单例模式使用的场景:
- 需要频繁的进行创建和销毁的对象;
- 创建对象时耗时过多或耗费资源过多(即:重量级对象);
- 经常用到的对象、工具类对象、频繁访问数据库或文件的对象(比如数据源、 session工厂等)