单例设计模式
- 一个类在任何情况下都绝对只有一个实例
- 提供全局访问点
- 例如ServletContext、ServletContextConfig、ApplicationContext,数据库连接池。
1、饿汉式单例
类加载时,即初始化,并且创建单例对象。
适用于单例较少的情况
SpringIoC容器ApplicationContext就是典型的饿汉单例模式。
优点:
-
因为在线程还没出现时就创建了对象,所以是
线程安全
的; - 没有加锁,执行效率高
缺点:
- 运行期间一直占用内存,不使用时浪费内存。
实现饿汉单例
实现一:
public class HungrySingleton {
//先静态、后动态
//先属性,后方法
//先上后下
private static final HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton() {
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
实现二:
public class HungrySingleton {
private static HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){};
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
2、懒汉式单例模式
-
不是线程安全的
-
被外部类调用时内部类才会加载
public class LazySimpleSingleton {
private static LazySimpleSingleton lazySimpleSingleton = null;
private LazySimpleSingleton() {
}
public static LazySimpleSingleton getInstance(){
if(lazySimpleSingleton==null) lazySimpleSingleton=new LazySimpleSingleton();
return lazySimpleSingleton;
}
}
方法一:
注:线程状态下可能会得到两个不相同的实例,需要在getInstance()方法加上synchronized
public class LazySimpleSingleton {
private static LazySimpleSingleton lazySimpleSingleton = null;
private LazySimpleSingleton() {
}
public synchronized static LazySimpleSingleton getInstance(){
if(lazySimpleSingleton==null) lazySimpleSingleton=new LazySimpleSingleton();
return lazySimpleSingleton;
}
}
方法二:
上述方法仍存在问题,加锁之后线程多了会对CPU造成压力,使用双重检查锁
不对整个类上锁,而只是针对getInstance()方法,增加了性能。
package com.sy.day2.t5;
public class LazySimpleSingleton {
private static LazySimpleSingleton lazySimpleSingleton = null;
public LazySimpleSingleton() {
}
public static LazySimpleSingleton getInstance(){
if(lazySimpleSingleton==null) {
synchronized (LazySimpleSingleton.class){
if(lazySimpleSingleton==null) lazySimpleSingleton=new LazySimpleSingleton();
}
}
return lazySimpleSingleton;
}
}
方法三:
使用内部类初始化解决
//这种形式兼顾饿汉式单例模式的内存浪费问题和synchronized的性能问题
//完美屏蔽了这两个缺点
public class LazyInnerClassSingleton {
//使用LazyInnerClassGeneral的时候,默认会先初始化内部类
//如果没使用,则内部类时不加载的
private LazyInnerClassSingleton() {
}
//static是为了使单例的空间共享,保证这个方法不会被覆写、重载
public static final LazyInnerClassSingleton getInstance(){
//在返回结果以前,一定会先加载内部类
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
3、反射破坏单例模式
- 通过反射机制可以得到一个新的实例,这样就破坏了单例模式的概念。
测试问题:
package com.sy.day2.t5;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class LazyInnerClassSingletonTest {
public static void main(String[] args) {
try {
Class<?> clazz = LazyInnerClassSingleton.class;
//通过反射获取私有的构造方法
Constructor c = clazz.getDeclaredConstructor(null);
//强制访问
c.setAccessible(true);
//暴力初始化
Object o1 = c.newInstance();
//调用了两次构造方法,相当于“new”了两次
Object o2 = c.newInstance();
System.out.println(o1==o2);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
解决
:在构造方法中加入逻辑判断,单例对象为空时抛出异常
package com.sy.day2.t5;
//这种形式兼顾饿汉式单例模式的内存浪费问题和synchronized的性能问题
//完美屏蔽了这两个缺点
public class LazyInnerClassSingleton {
//使用LazyInnerClassGeneral的时候,默认会先初始化内部类
//如果没使用,则内部类时不加载的
private LazyInnerClassSingleton() {
if(LazyHolder.LAZY!=null){
throw new RuntimeException("不允许创建多个实例");
}
}
//static是为了使单例的空间共享,保证这个方法不会被覆写、重载
public static final LazyInnerClassSingleton getInstance(){
//在返回结果以前,一定会先加载内部类
return LazyHolder.LAZY;
}
//默认不加载
private static class LazyHolder{
private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
}
}
4、反序列化破坏单例模式
问题
:对象序列化存入磁盘后,下次进行反序列化时,如果不加限制反序列化所得到的对象与当前对象并不相同,违背了单例设计模式。
测试:
package com.sy.day2.t5;
import java.io.Serializable;
public class SeriableSingleton implements Serializable {
//序列化就是把内存中的状态通过转换成字节码的形式
//从而转换一个I/O流,写入其他地方(可以是磁盘,网络I/O)
//内存中的状态持久化
//反序列化就是将持久化的字节码内容转换成I/O流
//通过I/O流的读取,进而将读取的内容转换为Java对象
//转换过程中会重新创建对象(相当于重新new一个对象)
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton() {}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
}
package com.sy.day2.t5;
import java.io.*;
public class SeriableSingletonTest {
public static void main(String[] args) {
SeriableSingleton s1 = null;
SeriableSingleton s2 = SeriableSingleton.getInstance();
FileOutputStream fos = null;
try {
fos = new FileOutputStream("SeriableSingleton.obj");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(s2);
oos.flush();
oos.close();
FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
ObjectInputStream ois = new ObjectInputStream(fis);
s1 = (SeriableSingleton) ois.readObject();
ois.close();
System.out.println(s1);
System.out.println(s2);
System.out.println(s1==s2);
} catch (Exception e) {
e.printStackTrace();
}
}
}
解决
:实现Serializable接口,增加readResolve方法
package com.sy.day2.t5;
import java.io.Serializable;
public class SeriableSingleton implements Serializable {
//序列化就是把内存中的状态通过转换成字节码的形式
//从而转换一个I/O流,写入其他地方(可以是磁盘,网络I/O)
//内存中的状态持久化
//反序列化就是将持久化的字节码内容转换成I/O流
//通过I/O流的读取,进而将读取的内容转换为Java对象
//转换过程中会重新创建对象
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton() {}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
private Object readResolve(){
return INSTANCE;
}
}
注:虽然解决了单例设计模式被破坏的问题,但实际上是实例化了两次,只是readResolve方法覆盖了第一次的对象。
5、注册式单例设计模式
实现一、枚举式
- 将 每一个实例都登记到一个地方,以唯一标识符获取
- 分两种模式:①枚举式单例 ②容器里式单例
枚举单例模式,由于枚举在JDK语法的特殊性及反射为枚举做的保护。使得枚举单例做为推荐的单例实现方法。
public enum EnumSingleton {
INSTANCE;
private Object data;
private Object getData() {
return data;
}
public void setData(Object data){
this.data = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
实现二、容器式单例
- 使用广泛,但非线程安全。
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String,Object>();
private static Object getBean(String className){
synchronized (ioc){
if(!ioc.containsKey(className)){
Object obj = null;
try {
obj = Class.forName(className).newInstance();
ioc.put(className,obj);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}else {
return ioc.get(className);
}
}
}
}
6、线程单例实现ThreadLocal
- 不保证其全局唯一,但保证在单个线程中是唯一的,天生线程安全。
-
如何实现:ThreadLocal将所有对象放入ThreadLocalMap中,为
每个线程提供一个对象
,
以空间换时间
。
public class ThreadLocalSingleton {
//将实例对象放入ThreadLocal对象中,外部取时通过ThreadLocal对象获得。
private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstance =
new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton() {}
public static ThreadLocalSingleton getInstance(){
return threadLocalInstance.get();
}
}