使用场景
现实中的单例:
系统可以有多个打印任务,但只能有一个打印管理器,否则打印机就不知道打印谁的信息。
一个系统只能有一个任务管理器,如果有多个任务管理器,如果这些窗口显示的内容一致就意味着浪费资源,如果显示的内容不一样表示同一时刻系统有两个不同的状态。
代码中的单例:
需要写一个解析XML文件类,且这个类使用频繁。我们每次使用就得实例化一个对象,浪费时间空间,加重了GC的负担。
上述问题解决:
在逻辑和代码中只允许实例化一个对象,每次使用都得使用这一个对象。
单例模式就是:一个类保证只有一个实例,并提供一个全局的访问接口。
单例一个在代码中在工具类时使用,在使用单例是必须注意,单例类的数据成员的初始化问题,如果你一个类两次使用的对象的属性值不同的话,尽量少用单例,或者使用时注意。
知识储备
构造函数私有化
构造函数私有化后表示只能后在类内调用,如下:
public class WqClass {
private WqClass(){
}
private void instantiation(){
WqClass myInner = new WqClass();
}
}
静态成员
静态成员是属于类的而不属于某个对象,调用时使用“类名.静态数据成员”或者“类名.静态成员函数名(形参列表)”。同样使用对象也可以访问。
public class WqClass {
public static Double PI=3.14;
public static wqStack Instance = null;
public static void fun(){
Instance = new wqStack();
}
public static void main(String [] args){
WqClass.PI = WqClass.PI;
WqClass.fun();
WqClass myInner = new WqClass();
myInner.PI = myInner.PI;
myInner.fun();
}
}
延迟加载(Lazy Loading)
延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。
public class LazyLoadingClass {
public static LazyLoadingClass Instance = null;
public static void getInstance(){
Instance = new LazyLoadingClass();
}
}
缓存
当一些数据频繁使用时,应该将这些数据放在内存中。缓存是一种典型的空间换时间的方法。
public class JavaCashClass {
private Map<String, String> holderMap = new HashMap<String, String>();
public String getRE(String key){
String reStr="";
//读取缓存
if(holderMap.get(key)!=null){
reStr=holderMap.get(key);
}else {
//从数据库读取数据
String value=getStrFormDB(key);
if(!"".equals(value)){
//将新数据存入缓存
holderMap.put(key, value);
reStr=value;
}
}
return reStr;
}
private String getStrFormDB(String key){
return "string";
}
}
静态内部类
内部类是在一个类的内部在定义一个类,这个类是外部类的成员,所以可以通过外部类访问。且这个成员是静态的,即可以通过外部类的类名来访问内部类。
示例:
public class OuterClass {
public static class InnerClass{
public static double PI = 3.14;
}
}
访问:
OuterClass.InnerClass.PI++;
延迟加载就是当在真正需要数据的时候,才真正执行数据加载操作。
public class LazyLoadingClass {
public static LazyLoadingClass Instance = null;
public static void getInstance(){
Instance = new LazyLoadingClass();
}
}
Synchronized关键字
Synchronized关键字是使用在多线程中为了保证同步安全的同步方法和同步方法块中。
public class LazyLoadingClass {
public static LazyLoadingClass Instance = null;
public static Synchronized void getInstance(){
Instance = new LazyLoadingClass();
}
public static void getInstance(){
Synchronized (Instance ){
Instance = new LazyLoadingClass();
}
}
}
枚举应用扩展
public class CSingleton {
public static CSingleton Instance = new CSingleton("WqCSingletonInstance");
public static CSingleton getIntance(){
return Instance;
}
private CSingleton(String nameV){
this.iName=nameV;
}
private String iName;
public String getName(){
return iName;
}
}
访问:
CSingleton cSingleton=CSingleton.getIntance();
System.out.println(cSingleton.getName());
单例的实现
单例模式的实现主要包括以下几种:懒汉式、饿汉式、双重校验锁实现、静态内部类实现、枚举实现。
懒汉式
懒汉式实现方式是现在一般程序中使用较多的一种,特点是典型,同时这种实现也存在一定问题。以下实现步骤:
1)私有化构造方法
要想控制实例化的次数就必须收回类的实例化权利,私有化构造函数使得只能在内部实例化对象。
private Singleton(){
}
2)提供获取实例的方法
定义一个类为的是使用,当构造函数私有化后就导致在类外就不能实例化对象了,就必须给该类添加一个的方法来获取示例。
public Singleton getInstance(){
}
3)把获取实例的方法变成静态的
要想调用第二步中的getInstance方法就必须使用对象来调用,但在类外是没法实例化对象的,所以想要调用就必须静态化。
private static Singleton getInstance(){ }
4)定义存储实例的属性
接口定义好了,那内部怎么实例化呢?直接在getInstance实例化返回?代码如:
public static Singleton getInstance(){
return new Singleton();
}
这样可以获取到对象,但没有达到单例的要求,每次调用就会新产生一个对象。如何实现每次调用都是返回的同一个实例呢?使用缓存的思想定义一个存储实例属性成员。
private Singleton instance = null;
且这个变量也要是一个静态成员,至于为什么?反证一下。
private static Singleton instance = null;
5)实现控制实例的创建
public static Singleton getInstance(){
//先判断instance是否有值
if(instance == null){
//如果没有值,说明还没有创建过实例,那就创建一个
//并把这个实例设置给instance
instance = new Singleton ();
}
//如果有值,或者是创建了值,那就直接使用
return instance;
}
懒汉式的完整实现:
public class SingletonA {
private static SingletonA Instance=null;
private SingletonA(){}
public SingletonA getInstance(){
if(Instance==null){
Instance=new SingletonA();
}
return Instance;
}
}
懒汉式采取了延迟加载(懒加载 Lazy Loading)的思想,代码比较有代表性,但多线程编程时不安全。
饿汉式
public class SingletonB {
/**
* 饿汉式
* 同步安全,使用了JVM保证了只能实例化一个SingletonB对象
*/
private static SingletonB Instance = new SingletonB();
private SingletonB(){}
public SingletonB getInstance(){
return Instance;
}
}
简述:之所以叫饿汉式,因为他在加载时加实例化了。没有使用延迟加载的思想,但JVM加载类是多线程安全的,就保证了这种实现方式多线程安全。
双重校验锁实现
这种实现方式是用来保证多线程安全,使用的同步方法和同步方法块来实现。
我们懒汉式是多线程不安全的,那我们就在懒汉式就多了一个synchronized关键字来保证同步安全。但这种实现方式影响效率,原因是每次都得判断所需资源是否空闲。
public class SingletonS {
private static SingletonS Instance = null;
private SingletonS(){}
public synchronized SingletonS getInstance(){
if(Instance==null){
Instance=new SingletonS();
}
return Instance;
}
}
改进:
使用同步方法块替代同步方法,即只有实例化时才保证同步安全就可以。代码如:
public Singleton getInstance(){
if(Instance==null){
synchronized(Instance){
Instance=new SingletonS();
}
}
return Instance;
}
这样效率是上来了,但并不能保证同步安全了,想两个线程同时进了if。那也是一个一个实例化,一共实例化了两个。
继续改进:
不能保证同步安全是因为两个线程都进入了if。实例化只是先后的事,并没有保证一个对象。所以在内层在进行一下判断,保证对象只实例化一个。
public Singleton getInstance(){
if(Instance==null){//保证效率
synchronized(Instance){
if(Instance==null){//保证对象唯一
Instance=new SingletonS();
}
}
}
return Instance;
}
简述:使用了延迟加载,同时保证了同步安全。但相比之下还是影响效率。
静态内部类实现
这种方法时上一钟的一种简化,巧妙应用了Jvm加载类时多线程安全的特性,来保证单例实现时实例化对象时的多线程安全性。
public class SingletonHolder {
private SingletonHolder(){
}
/**
* 定义内部类,是用Jvm保证实例化时的同步安全
*/
private static class Holder{
private static SingletonHolder instance = new SingletonHolder();
}
public SingletonHolder getInstance(){
return Holder.instance;
}
}
枚举实现
/**
* 使用枚举来实现单例模式的示例
*/
public enum Singleton {
/**
* 定义一个枚举的元素,它就代表了Singleton的一个实例
*/
uniqueInstance;
/**
* 示意方法,单例可以有自己的操作
*/
public void singletonOperation(){
//功能处理
}
}
简述:使用枚举来实现单实例控制,会更加简洁,而且无偿的提供了序列化的机制,并由JVM从根本上提供保障,绝对防止多次实例化,是更简洁、高效、安全的实现单例的方式
单例模式的扩展
单例模式的本质是控制实例数目唯一。那我要是只要确保3个示例呢?怎么实现呢??
概括为增加缓存对象,修改个数控制函数逻辑。
其实思路很简单,就是通过Map来缓存实现单例,进行变形,一个Map可以缓存任意多个实例,新的问题就是,Map中有多个实例,但是客户 端调用的时候,到底返回那一个实例呢,也就是实例的调度问题,我们只是想要来展示设计模式,对于这个调度算法就不去深究了。