原博文:https://blog.csdn.net/KongZhongNiao/article/details/79603446
定义
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
结构和说明
Singleton:
负责创建Singleton类自己的唯一实例,并提供一个getInstance的方法,
让外部来访问这个类的唯一实例。
示例代码
单例示例 懒汉式
package cn.javass.dp.singleton.example2;
/**
* 单例示例 懒汉式
*/
public class Singleton {
/**
* 定义一个变量来存储创建好的类实例
*/
private static Singleton uniqueInstance = ;
/**
* 私有化构造方法,好在内部控制创建实例的数目
*/
private Singleton(){
//
}
/**
* 定义一个方法来为客户端提供类实例
* @return 一个Singleton的实例
*/
public static synchronized Singleton getInstance(){
//判断存储实例的变量是否有值
if(uniqueInstance == ){
//如果没有,就创建一个类实例,并把值赋值给存储类实例的变量
uniqueInstance = new Singleton();
}
//如果有值,那就直接使用
return uniqueInstance;
}
/**
* 示意方法,单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
}
/**
* 示意属性,单例可以有自己的属性
*/
private String singletonData;
/**
* 示意方法,让外部通过这些方法来访问属性的值
* @return 属性的值
*/
public String getSingletonData(){
return singletonData;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
饿汉式单例实现的示例
package cn.javass.dp.singleton.example3;
/**
* 饿汉式单例实现的示例
*/
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;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
体会单例模式
读取配置文件的内容
现在要读取配置文件的内容,该如何实现呢?
不用模式的解决方案
直接参看代码示例
读取应用配置文件
package cn.javass.dp.singleton.example1;
import java.io.*;
import java.util.*;
/**
* 读取应用配置文件
*/
public class AppConfig {
/**
* 用来存放配置文件中参数A的值
*/
private String parameterA;
/**
* 用来存放配置文件中参数B的值
*/
private String parameterB;
public String getParameterA() {
return parameterA;
}
public String getParameterB() {
return parameterB;
}
/**
* 构造方法
*/
public AppConfig(){
//调用读取配置文件的方法
readConfig();
}
/**
* 读取配置文件,把配置文件中的内容读出来设置到属性上
*/
private void readConfig(){
Properties p = new Properties();
InputStream in = ;
try {
System.out.println("读取 配置文件一次=============");
in = AppConfig.class.getResourceAsStream("AppConfig.properties");
p.load(in);
//把配置文件中的内容读出来设置到属性上
this.parameterA = p.getProperty("paramA");
this.parameterB = p.getProperty("paramB");
} catch (IOException e) {
System.out.println("装载配置文件出错了,具体堆栈信息如下:");
e.printStackTrace();
}finally{
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
配置文件 AppConfig.properties
paramA=a1
paramB=b2
- 1
- 2
客户端
package cn.javass.dp.singleton.example1;
public class Client {
public static void main(String[] args) {
//创建读取应用配置的对象
AppConfig config = new AppConfig();
String paramA = config.getParameterA();
String paramB = config.getParameterB();
System.out.println("paramA="+paramA+",paramB="+paramB);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
存在的问题
在系统运行期间,系统中会存在很多个AppConfig的实例对象,这会严重浪
费系统资源。
把上面的描述进一步抽象一下,问题就出来了:在一个系统运行期间,某
个类只需要一个类实例就可以了,那么应该怎么实现呢?
使用模式的解决方案
直接参看代码示例
读取应用配置文件,单例实现
package cn.javass.dp.singleton.example4;
import java.io.*;
import java.util.*;
/**
* 读取应用配置文件,单例实现
*/
public class AppConfig {
/**
* 定义一个变量来存储创建好的类实例,直接在这里创建类实例,只会创建一次
*/
private static AppConfig instance = new AppConfig();
/**
* 定义一个方法来为客户端提供AppConfig类的实例
* @return 一个AppConfig的实例
*/
public static AppConfig getInstance(){
return instance;
}
/**
* 用来存放配置文件中参数A的值
*/
private String parameterA;
/**
* 用来存放配置文件中参数B的值
*/
private String parameterB;
public String getParameterA() {
return parameterA;
}
public String getParameterB() {
return parameterB;
}
/**
* 私有化构造方法
*/
private AppConfig(){
//调用读取配置文件的方法
readConfig();
}
/**
* 读取配置文件,把配置文件中的内容读出来设置到属性上
*/
private void readConfig(){
Properties p = new Properties();
InputStream in = AppConfig.class.getResourceAsStream("AppConfig.properties");
try {
System.out.println("现在读取配置文件一次============");
p.load(in);
//把配置文件中的内容读出来设置到属性上
this.parameterA = p.getProperty("paramA");
this.parameterB = p.getProperty("paramB");
} catch (IOException e) {
System.out.println("装载配置文件出错了,具体堆栈信息如下:");
e.printStackTrace();
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
配置文件 AppConfig.properties
paramA=a1
paramB=b2
- 1
- 2
客户端
package cn.javass.dp.singleton.example4;
public class Client {
public static void main(String[] args) {
//创建读取应用配置的对象
for(int i=0;i<5;i++){
AppConfig config = AppConfig.getInstance();
String paramA = config.getParameterA();
String paramB = config.getParameterB();
System.out.println("i="+i+"paramA="+paramA+",paramB="+paramB);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
理解单例模式
认识单例模式
1:单例模式的功能
单例模式的功能是用来保证这个类在运行期间只会被创建一个类实例,并
提供一个全局唯一访问这个类实例的访问点。
2:单例模式的范围
是一个ClassLoader及其子ClassLoader的范围
3:单例模式的命名
一般建议单例模式的方法命名为:getInstance() 。
单例模式的名称:单例、单件、单体等等,翻译的不同,都是指的同一个模式
懒汉式和饿汉式实现
分步骤代码示例
懒汉式
package cn.javass.dp.singleton.example5;
//懒汉式
public class Singleton {
//4:定义一个变量来存储创建好的类实例
//5:因为这个变量要在静态方法中使用,所以需要加上static修饰
private static Singleton instance = ;
//1:私有化构造方法,好在内部控制创建实例的数目
private Singleton(){
}
//2:定义一个方法来为客户端提供类实例
//3:这个方法需要定义成类方法,也就是要加static
public static Singleton getInstance(){
//6:判断存储实例的变量是否有值
//B
if(instance == ){//B
//6.1:如果没有,就创建一个类实例,并把值赋值给存储类实例的变量
//B
//A
instance = new Singleton();//A
}
return instance;
}
public static void main(String[] args) {
for(int i=0;i<3;i++){
System.out.println(Singleton.getInstance());
}
}
}
//从时间空间上看:以时间换空间
//从线程安全上看:不安全的package cn.javass.dp.singleton.example5;
//懒汉式
public class Singleton {
//4:定义一个变量来存储创建好的类实例
//5:因为这个变量要在静态方法中使用,所以需要加上static修饰
private static Singleton instance = ;
//1:私有化构造方法,好在内部控制创建实例的数目
private Singleton(){
}
//2:定义一个方法来为客户端提供类实例
//3:这个方法需要定义成类方法,也就是要加static
public static Singleton getInstance(){
//6:判断存储实例的变量是否有值
//B
if(instance == ){//B
//6.1:如果没有,就创建一个类实例,并把值赋值给存储类实例的变量
//B
//A
instance = new Singleton();//A
}
return instance;
}
public static void main(String[] args) {
for(int i=0;i<3;i++){
System.out.println(Singleton.getInstance());
}
}
}
//从时间空间上看:以时间换空间
//从线程安全上看:不安全的
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
饿汉式
package cn.javass.dp.singleton.example6;
//饿汉式
public class Singleton {
//4:定义一个静态变量来存储创建好的类实例
//直接在这里创建类实例,由虚拟机来保证只会创建一次
private static Singleton instance = new Singleton();
//1:私有化构造方法,好在内部控制创建实例的数目
private Singleton(){
}
//2:定义一个方法来为客户端提供类实例
//3:这个方法需要定义成类方法,也就是要加static
public static Singleton getInstance(){
//5:直接使用已经创建好的实例
return instance;
}
public static void main(String[] args) {
for(int i=0;i<3;i++){
System.out.println(Singleton.getInstance());
}
}
}
//从时间空间上看:以空间换时间
//从线程安全上看:安全的
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
单例模式的调用顺序示意图
延迟加载的思想
什么是延迟加载呢?
通俗点说,就是一开始不要加载资源或者数据,一直等到马上就要使用这
个资源或者数据了,躲不过去了才加载,所以也称Lazy Load,不是懒惰啊,是
“延迟加载”,这在实际开发中是一种很常见的思想,尽可能的节约资源。
缓存的思想
单例模式的懒汉式实现还体现了缓存的思想,缓存也是实际开发中非常常
见的功能。
简单讲就是,如果某些资源或者数据会被频繁的使用,可以把这些数据缓
存到内存里面,每次操作的时候,先到内存里面找,看有没有这些数据,如果
有,那么就直接使用,如果没有那么就获取它,并设置到缓存中,下一次访问的
时候就可以直接从内存中获取了。从而节省大量的时间,当然,缓存是一种典型
的空间换时间的方案。
Java中缓存的基本实现
直接参看代码示例
package cn.javass.dp.singleton.example7;
import java.util.*;
/**
* Java中缓存的基本实现示例
*/
public class JavaCache {
/**
* 缓存数据的容器,定义成Map是方便访问,直接根据Key就可以获取Value了
* key选用String是为了简单,方便演示
*/
private Map<String,Object> map = new HashMap<String,Object>();
/**
* 从缓存中获取值
* @param key 设置时候的key值
* @return key对应的Value值
*/
public Object getValue(String key){
//先从缓存里面取值
Object obj = map.get(key);
//判断缓存里面是否有值
if(obj == ){
//如果没有,那么就去获取相应的数据,比如读取数据库或者文件
//这里只是演示,所以直接写个假的值
obj = key+",value";
//把获取的值设置回到缓存里面
map.put(key, obj);
}
//如果有值了,就直接返回使用
return obj;
}
//1:定义一个存放缓存数据的容器
//2:从缓存中获取数据的做法
//2.1:先从缓存里面取值
//2.2:判断缓存里面是否有值
//2.3:如果有值了,就直接使用这个值
//2.4:如果没有,那么就去获取相应的数据,或者是创建相应的对象
//2.4.1:把获取的值设置回到缓存里面
//web开发 Scope===〉就是数据的缓存范围
//<jsp:useBean name="aa" class="cn.javass.AModel" scope="request">
// Object obj = request.getAttribute("aa");
// AModel am = null;
// if(obj==null){
// am = new AModel();
// request.setAttribute("aa",am);
// }else{
// am = (AModel)obj
// }
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
利用缓存来实现单例模式
直接参看代码示例
package cn.javass.dp.singleton.example8;
import java.util.*;
/**
* 使用缓存来模拟实现单例
*/
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==){
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());
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
单例模式的优缺点
1:时间和空间:懒汉式是典型的时间换空间,饿汉式是典型的空间换时间
2:线程安全 :
(1)不加同步的懒汉式是线程不安全的
(2)饿汉式是线程安全的,因为虚拟机保证了只会装载一次
(3)如何实现懒汉式的线程安全呢?
加上synchronized即可
(4)双重检查加锁
所谓双重检查加锁机制,指的是:并不是每次进入getInstance方法都需要
同步,而是先不同步,进入方法过后,先检查实例是否存在,如果不存在才进入
下面的同步块,这是第一重检查。进入同步块过后,再次检查实例是否存在,如
果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只
需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。
双重检查加锁机制的实现会使用一个关键字volatile,它的意思是:被
volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直
接操作共享内存,从而确保多个线程能正确的处理该变量。
注意:在Java1.4及以前版本中,很多JVM对于volatile关键字的实现有问
题,会导致双重检查加锁的失败,因此本机制只能用在Java5及以上的版本。
双重检查加锁示例代码
package cn.javass.dp.singleton.example10;
public class Singleton {
/**
* 对保存实例的变量添加volatile的修饰
*/
private volatile static Singleton instance = ;
private Singleton(){
}
public static Singleton getInstance(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(instance == ){
//同步块,线程安全的创建实例
synchronized(Singleton.class){
//再次检查实例是否存在,如果不存在才真的创建实例
if(instance == ){
instance = new Singleton();
}
}
}
return instance;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
在Java中一种更好的单例实现方式
Lazy initialization holder class模式,这个模式综合使用了Java的类级内部
类和多线程缺省同步锁的知识,很巧妙的同时实现了延迟加载和线程安全。
内部类示例代码
package cn.javass.dp.singleton.example11;
public class Singleton {
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,
* 而且只有被调用到才会装载,从而实现了延迟加载
*/
private static class SingletonHolder{
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static Singleton instance = new Singleton();
}
/**
* 私有化构造方法
*/
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
单例和枚举
按照《高效Java 第二版》中的说法:单元素的枚举类型已经成为实现
Singleton的最佳方法。
为了理解这个观点,先来了解一点相关的枚举知识,这里只是强化和总结
一下枚举的一些重要观点,更多基本的枚举的使用,请参看Java编程入门资料:
- (1)Java的枚举类型实质上是功能齐全的类,因此可以有自己的属性和方法
- (2)Java枚举类型的基本思想:通过公有的静态final域为每个枚举常量导出实
例的类
- (3)从某个角度讲,枚举是单例的泛型化,本质上是单元素的枚举
用枚举来实现单例非常简单,只需要编写一个包含单个元素的枚举类型即可
枚举示例代码
package cn.javass.dp.singleton.example12;
/**
* 使用枚举来实现单例模式的示例
*/
public enum Singleton {
/**
* 定义一个枚举的元素,它就代表了Singleton的一个实例
*/
uniqueInstance;
/**
* 示意方法,单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
System.out.println("aa=="+Singleton.uniqueInstance.hashCode());
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
思考单例模式
单例模式的本质
单例模式的本质是:控制实例数目
利用缓存实现控制控制数据
package cn.javass.dp.singleton.example9;
import java.util.*;
/**
* 简单演示如何扩展单例模式,控制实例数目为3个
*/
public class OneExtend {
/**
* 定义一个缺省的key值的前缀
*/
private final static String DEFAULT_PREKEY = "Cache";
/**
* 缓存实例的容器
*/
//实例调度的问题
private static Map<String,OneExtend> map = new HashMap<String,OneExtend>();
/**
* 用来记录当前正在使用第几个实例,到了控制的最大数目,就返回从1开始
*/
private static int num = 1;
/**
* 定义控制实例的最大数目
*/
private final static int NUM_MAX = 3;
private OneExtend(){}
public static OneExtend getInstance(){
String key = DEFAULT_PREKEY+num;
OneExtend oneExtend = map.get(key);
if(oneExtend==){
oneExtend = new OneExtend();
map.put(key, oneExtend);
}
//把当前实例的序号加1
num++;
if(num > NUM_MAX){
//如果实例的序号已经达到最大数目了,那就重复从1开始获取
num = 1;
}
return oneExtend;
}
public static void main(String[] args) {
OneExtend t1 = getInstance();
OneExtend t2 = getInstance();
OneExtend t3 = getInstance();
OneExtend t4 = getInstance();
OneExtend t5 = getInstance();
OneExtend t6 = getInstance();
System.out.println("t1=="+t1);
System.out.println("t2=="+t2);
System.out.println("t3=="+t3);
System.out.println("t4=="+t4);
System.out.println("t5=="+t5);
System.out.println("t6=="+t6);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
何时选用单例模式
当需要控制一个类的实例只能有一个,而且客户只能从一个全局访问点访
问它时,可以选用单例模式,这些功能恰好是单例模式要解决的问题