保证一个类只有一个实例,并提供一个唯一的全局访问点。
1、介绍
单例模式的功能
保证这个类在运行期间只会被创建一个类实例,并提供一个全局唯一访问这个类实例的访问点。
单例模式的范围
是一个ClassLoader及其子ClassLoader的范围。
单例模式的命名
一般建议单例模式的方法命名为:getInstance()。
单例模式的本质
控制实例数目
何时选用单例模式
当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访问它时,可以选择单例模式。
单例模式使用场景
- 配置文件的读取,由于配置文件是共享资源。
- 应用程序的日志记录,日志属于共享文件,只能有一个实例去操作。
- 数据库连接池,多线程线程池。
- 网站计数器,否则难以同步。
2、结构说明
Singleton:
负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,让外部来访问这个类的唯一实例。
3、单例模式实现方式
懒汉式、饿汉式、双重检查加锁、枚举类实现
单例模式调用顺序示意图
一种更好的单例实现方式
Lazy initialization holder class模式,这个模式综合使用了Java的类级内部
类和多线程缺省同步锁的知识,很巧妙的同时实现了延迟加载和线程安全。
单例和枚举
Singleton的最佳方法。
(1)Java的枚举类型实质上是功能齐全的类,因此可以有自己的属性和方法
(2)Java枚举类型的基本思想:通过公有的静态final域为每个枚举常量导出实
例的类
(3)从某个角度讲,枚举是单例的泛型化,本质上是单元素的枚举
用枚举来实现单例非常简单,只需要编写一个包含单个元素的枚举类型即可
4、单例模式中的编程思想
延迟加载思想
一开始不加载资源或数据,一直等到马上要使用这个资源或数据了才去加载,也称Lazy Load,尽可能节约资源。
缓存思想
某些资源或数据会被频繁使用,可以将这些数据缓存到内存中,每次操作时,先到内存里面找,如果有就直接使用,如果没有就获取它,并设置到缓存中,下一次访问的时候就可以直接从内存中获取了。从而节省大量的时间,缓存是一种典型的空间换时间的方案。
5、单例模式优缺点
- 时间和空间:懒汉式典型的时间换空间,饿汉式空间换时间。
- 线程安全:
不加同步的懒汉式是线程不安全的
饿汉式是线程安全的,因为虚拟机保证了只会装载一次。
如何实现懒汉式线程安全,加上synchronized即可。
双重检查锁
所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入下面的同步块,这是第一重检查。进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
注意:在Java1.4及以前版本中,很多JVM对于volatile关键字的实现有问题,会导致双重检查加锁的失败,因此本机制只能用在Java5及以上的版本。
6、代码示例
/**
* 使用枚举来实现单例模式的示例
*/
public enum Singleton {
/**
* 定义一个枚举的元素,它就代表了Singleton的一个实例
*/
uniqueInstance;
/**
* 示意方法,单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
System.out.println("aa=="+Singleton.uniqueInstance.hashCode());
}
}
/**
* 单例示例 懒汉式
*/
public class Singleton {
/**
* 定义一个变量来存储创建好的类实例
*/
private static Singleton uniqueInstance = null;
/**
* 私有化构造方法,好在内部控制创建实例的数目
*/
private Singleton(){
}
/**
* 定义一个方法来为客户端提供类实例
* @return 一个Singleton的实例
*/
public static synchronized Singleton getInstance(){
//判断存储实例的变量是否有值
if(uniqueInstance == null){
//如果没有,就创建一个类实例,并把值赋值给存储类实例的变量
uniqueInstance = new Singleton();
}
//如果有值,那就直接使用
return uniqueInstance;
}
/**
* 示意方法,单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
}
/**
* 示意属性,单例可以有自己的属性
*/
private String singletonData;
/**
* 示意方法,让外部通过这些方法来访问属性的值
* @return 属性的值
*/
public String getSingletonData(){
return singletonData;
}
}
/**
* 饿汉式单例实现的示例
*/
public class Singleton {
/**
* 定义一个变量来存储创建好的类实例,直接在这里创建类实例,只会创建一次
*/
private static Singleton uniqueInstance = new Singleton();
/**
* 私有化构造方法,好在内部控制创建实例的数目
*/
private Singleton(){
}
/**
* 定义一个方法来为客户端提供类实例
* @return 一个Singleton的实例
*/
public static Singleton getInstance(){
//直接使用已经创建好的实例
return uniqueInstance;
}
/**
* 示意方法,单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
}
/**
* 示意属性,单例可以有自己的属性
*/
private String singletonData;
/**
* 示意方法,让外部通过这些方法来访问属性的值
* @return 属性的值
*/
public String getSingletonData(){
return singletonData;
}
}
/**
* 使用缓存来模拟实现单例
*/
public class Singleton {
/**
* 定义一个缺省的key值,用来标识在缓存中的存放
*/
private final static String DEFAULT_KEY = "One";
/**
* 缓存实例的容器
*/
private static Map<String,Singleton> map = new HashMap<String,Singleton>();
/**
* 私有化构造方法
*/
private Singleton(){
//
}
public static Singleton getInstance(){
//先从缓存中获取
Singleton instance = (Singleton)map.get(DEFAULT_KEY);
//如果没有,就新建一个,然后设置回缓存中
if(instance==null){
instance = new Singleton();
map.put(DEFAULT_KEY, instance);
}
//如果有就直接使用
return instance;
}
public static void main(String[] args) {
for(int i=0;i<3;i++){
System.out.println(Singleton.getInstance());
}
}
}