概念和图解
概念
单例设计模式定义:确保这个类只有一个实例,并且自动的实例化向系统提供这个实例。
图解
经典代码
package com.cloud.day1; public class Singleton { //使用精态变量记录唯一实例 private static Singleton singleton; //构造器定义为私有的只有内部才可以使用 private Singleton(){} public static Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); } return singleton; } public void otherMethon(){ System.out.println("其他方法..."); } } |
说明:
Singleton称为单例类,构造函数使用private修饰,确保系统中只能产生一个实例,并且自动生成的。上面代码也就是所谓的懒汉式加载:只有到使用该对象的时候才来创建,意思饿了才来做饭吃。
线程安全问题
在上面的代码中存在一个很明显的线程安全问题,当有多条线程来请求对象实例的时候,因为对象的创建是需要时间的,假设A线程进来判断singleton == null,就会进入对象的创建过程,这时如果同时在过来几条线程,那么他们都会得到一个对象实例,这个就是所谓的线程安全问题。
同步方法
package com.cloud.day1; public class Singleton { //使用精态变量记录唯一实例 private static Singleton singleton; //构造器定义为私有的只有内部才可以使用 private Singleton(){} //有线程要进来,必须等待其他线程离开才可以 public static synchronized Singleton getInstance(){ if(singleton == null){ singleton = new Singleton(); } return singleton; } } |
该方法会影响系统的性能
饿汉式加载
package com.cloud.day1; public class Singleton { //使用精态变量记录唯一实例 private static Singleton singleton = new Singleton(); //构造器定义为私有的只有内部才可以使用 private Singleton(){} public static Singleton getInstance(){ return singleton; } } |
这里先把对象创建出来,有需要直接使用,也就是意思先把饭做好,饿了就直接吃,所以叫饿汉式加载。
双重检查
package com.cloud.day1; public class Singleton { //使用精态变量记录唯一实例 //volatile可以确保当singleton被初始化后,多线程可以正确处理 private volatile static Singleton singleton; //构造器定义为私有的只有内部才可以使用 private Singleton(){} public static Singleton getInstance(){ //如果实例不存在,则进入同步区 if(singleton == null){ //只有第一次才会彻底执行这里面的代码 synchronized (Singleton.class) { //再次检查,不存在就创建 if(singleton == null){ singleton = new Singleton(); } } } return singleton; } } |
听说JVM垃圾回收机制会吃掉单例对象,这个是很早年代的bug了,现在已经没有了。
单例模式应用
真假美猴王
package com.cloud.exe; import java.util.ArrayList; import java.util.Random; public class Monkey { //两个美猴王,一个真一个假 private static int twoMonkey = 2; //定义一个容器存放两只猴子 private static ArrayList<Monkey> monkeyList = new ArrayList<Monkey>(); //定义容器存放两只美猴王的名字 private static ArrayList<String> nameList = new ArrayList<String>(); //定义两只美猴王 static{ for(int i=0;i<twoMonkey;i++){ monkeyList.add(new Monkey("美猴王"+(i+1)+"号")); } } //私有构造方法 private Monkey(){} private Monkey(String name){ nameList.add(name); } //随机获取一只美猴王 private static int numRandom = 0; public static Monkey getInstance(){ Random random = new Random(); numRandom = random.nextInt(twoMonkey); return monkeyList.get(numRandom); } //谁是真的美猴王 public void say(){ System.out.println(nameList.get(numRandom)+":我是真的美猴王!"); } public static void main(String[] args) { int shenxian = 9; for(int i=0;i<shenxian;i++){ Monkey monkey = Monkey.getInstance(); System.out.print("第"+(i+1)+"个神仙得到的回答:"); monkey.say(); } } } |
运行效果:
第1个神仙得到的回答:美猴王2号:我是真的美猴王!
第2个神仙得到的回答:美猴王1号:我是真的美猴王!
第3个神仙得到的回答:美猴王1号:我是真的美猴王!
第4个神仙得到的回答:美猴王2号:我是真的美猴王!
第5个神仙得到的回答:美猴王1号:我是真的美猴王!
第6个神仙得到的回答:美猴王2号:我是真的美猴王!
第7个神仙得到的回答:美猴王2号:我是真的美猴王!
第8个神仙得到的回答:美猴王2号:我是真的美猴王!
第9个神仙得到的回答:美猴王1号:我是真的美猴王!
到底谁是美猴王还是去问如来佛祖吧!!!
单例模式优缺点
优点:
1. 单例模式只会创建一个对象实例,减少内存消耗
2. 设置全局访问点,优化共享资源的访问
缺点:
1. 没有接口,很难扩展
2. 不利于测试
3. 与单一职责原则冲突
单例模式总结
spring中,每个Bean默认就是单例,这样Spring容器可以管理Bean的生命周期。这个有个注意点就是Java的JVM垃圾回收机制,当一个对象在内存中长期不使用的时候,就会被回收需要使用的时候再来创建,解决方案:
1、 容器存放单例对象,例如Spring中的Bean对象
2、 资源文件记录单例的状态,即使被销毁了重新创建数据不会丢失