1 初识单例模式:
定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点
结构:
参考实现:
public class Singleton {
//饿汉式 线程安全下 双重检查机制 两个判null 一个synchronized ;
//好处在于 线程安全 并 减少多次同步下进行判断所浪费的时间
/**
* 对保存实例的变量添加volatile的修饰 这样当一个线程修改后 别的线程能立马感知到
线程之间共享 volatile这个内存空间 并只有在jdk1.5之后才能使用
*/
private volatile static Singleton instance = null;
private Singleton(){
}
public static Singleton getInstance(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(instance == null){
//同步块,线程安全的创建实例
synchronized(Singleton.class){
//再次检查实例是否存在,如果不存在才真的创建实例
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
2 体会单例模式:
场景问题: 读取配置文件的内容(配置文件内容是固定的,在系统中,得到配置文件的实例类仅仅有一个即可)
不用模式的解决方案: 每次都要new对象 并从配置文件中加载, new出多少个对象就加载多少次;
使用模式的解决方案: 将类设计成静态类,那么对象就一个,加载仅仅加载一次配置文件
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(){
System.out.println("开始咯");
Properties p = new Properties();
InputStream in = AppConfig.class.getResourceAsStream("AppConfig.properties");
try {
p.load(in);
//把配置文件中的内容读出来设置到属性上
this.parameterA = p.getProperty("paramA");
this.parameterB = p.getProperty("paramB");
} catch (IOException e) {
System.out.println("装载配置文件出错了,具体堆栈信息如下:");
e.printStackTrace();
}
}
}
client:
public class Client {
public static void main(String[] args) {
//创建读取应用配置的对象
for(int i=0; i<3; i++){
AppConfig config = AppConfig.getInstance();
String paramA = config.getParameterA();
String paramB = config.getParameterB();
System.out.println("paramA="+paramA+",paramB="+paramB);
}
}
}
3 理解单例模式:
单例模式命名: 建议方法命名为: getInstance()
单例模式:
懒汉式:以时间换取空间(每次都执行if判定,如果有实例,则不new 否则new)
饿汉式:以空间换取时间(直接new出对象来,占用空间)
延迟加载:最开始不加载,直到马上要使用到时才加载。
缓存思想:将经常用到的数据放在内存中,需要用的时候去内存获取(空间换时间)
java中缓存的基本实现:
/**
* 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 == null){
//如果没有,那么就去获取相应的数据,比如读取数据库或者文件
//这里只是演示,所以直接写个假的值
obj = key+",value";
//把获取的值设置回到缓存里面
map.put(key, obj);
}
//如果有值了,就直接返回使用
return obj;
}
}
利用缓存实现单例模式:
public class cacheSingleton {
private static cacheSingleton instance;
private cacheSingleton(){}
public static Map cacheMap = new HashMap();
private static String key = "key1";
public static cacheSingleton getInStance(){
instance = (cacheSingleton) cacheMap.get(key);
if(instance == null){
instance = new cacheSingleton();
cacheMap.put(key, instance);
}
return instance;
}
}
单例模式优缺点:
懒汉式在多线程下是线程不安全的
但是饿汉式能够保证线程安全,JVM会保证只会装载一次
懒汉式多线程安全,只需在getInstance(){}内new 实例时 增加synchronized即可。
代码写法见文章最开头
java中一种更好的单例实现方式:(Lazy initialization holder class模式)
package cn.javass.dp.singleton.example11;
/**
* 0 如下写法好处:
* 1 实现延迟加载
* 2 实现线程安全
* @author zm
*
*/
public class Singleton {
/**
* 类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例没有绑定关系,
* 而且只有被调用到才会装载,从而实现了延迟加载
*/
private static class SingletonHolder{
/**
* 静态初始化器,由JVM来保证线程安全
*/
private static Singleton instance = new Singleton();
}
/**
* 私有化构造方法
*/
private Singleton(){
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}
单例和枚举: 单元素的枚举类型已经成为实现Singleton的最佳方法
/**
* 使用枚举来实现单例模式的示例
*/
public enum Singleton {
/**
* 定义一个枚举的元素,它就代表了Singleton的一个实例
*/
uniqueInstance;
/**
* 示意方法,单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
}
}
4 思考单例模式:
本质: 控制实例数目
何时选用: 大多应用在配置文件获取上吧
5 单例模式 扩展为3个写法:
package cn.javass.dp.singleton.example9;
import java.util.*;
/**
* 简单演示如何扩展单例模式,控制实例数目为3个
*
* 1 使用map
* 2 使用单例
* 3 设置单例个数,然后将每次生成的对象放在map中
*/
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==null){
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);
}
}
6 单例模式脑图: