单例模式
定义:保证一个类仅有一个实例,并提供一个全局访问点;
类型:创建型
适用场景:(1)想确保任何情况下都绝对只有一个实例
优点:(1)在内存里只有一个实例,减少了内存开销,(2)可以避免对资源的多重占用,(3)设置全局访问点,严格控制访问。
缺点:(1)没有接口,扩展困难
重点:(1)私有构造器,(2)线程安全,(3)延时加载,(4)序列化和反序列化安全,(5)反射
懒汉式
// 懒汉式 的标准写法:
public class LazyDoubleCheckSingleton {
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance(){
if(lazyDoubleCheckSingleton == null){
synchronized (LazyDoubleCheckSingleton.class){
if(lazyDoubleCheckSingleton == null){
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
//1.分配内存给这个对象
// 指令重排序 //3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址
//2.初始化对象
// intra-thread semantics
// ------//3.设置lazyDoubleCheckSingleton 指向刚分配的内存地址
}
}
}
return lazyDoubleCheckSingleton;
}
}
//有3点需要注意:
(1)synchronized (LazyDoubleCheckSingleton.class) 锁的是这个类对象。
(2)双if的判断,提高了性能
(3)volatile 关键字,防止指令重排序
(4)私有构造函数
基于静态内部类初始化的延时加载
public class StaticInnerClassSingleton {
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance(){
return InnerClass.staticInnerClassSingleton;
}
private StaticInnerClassSingleton(){
}
}
饿汉式
// 代码示例
public class HungrySingleton implements Serializable{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
// 注意 使用 final 关键字
// 也可以不使用静态代码块的方式,定义变量时直接new
序列化破坏单例模式原理解析及解决方案
利用序列化,和反序列化,得到的对象不是同一个。
public class HungrySingleton implements Serializable,Cloneable{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
// 在使用序列化时,注意使用下面这个函数,不然在反序列化后得到的对象和系列化得到的对象不一致。
private Object readResolve(){
return hungrySingleton;
}
}
// 测试代码:
public class Test {
public static void main(String[] args){
HungrySingleton instance = HungrySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton_file"));
oos.writeObject(instance);
File file = new File("singleton_file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
HungrySingleton newInstance = (HungrySingleton) ois.readObject();
System.out.println(instance.getData());
System.out.println(newInstance.getData());
System.out.println(instance.getData() == newInstance.getData());
}
}
反射攻击解决方案及原理分析
利用反射得到的对象不是同一个。
public class HungrySingleton implements Serializable,Cloneable{
private final static HungrySingleton hungrySingleton;
static{
hungrySingleton = new HungrySingleton();
}
private HungrySingleton(){
if(hungrySingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
public static HungrySingleton getInstance(){
return hungrySingleton;
}
// 在使用序列化时,注意使用下面这个函数,不然在反序列化后得到的对象和系列化得到的对象不一致。
private Object readResolve(){
return hungrySingleton;
}
}
// 测试代码:
public class Test {
public static void main(String[] args){
Class objectClass = HungrySingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true); //因为构造函数是私有的,所有设置权限
HungrySingleton newInstance = (HungrySingleton) constructor.newInstance();
HungrySingleton instance = HungrySingleton.getInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
// 这种解决方法,适合类加载的时候就把单例创建好了,也适用于: 静态内部类方式,如下:
public class StaticInnerClassSingleton {
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
public static StaticInnerClassSingleton getInstance(){
return InnerClass.staticInnerClassSingleton;
}
private StaticInnerClassSingleton(){
if(InnerClass.staticInnerClassSingleton != null){
throw new RuntimeException("单例构造器禁止反射调用");
}
}
}
// 测试代码:
public class Test {
public static void main(String[] args){
Class objectClass = StaticInnerClassSingleton.class;
Constructor constructor = objectClass.getDeclaredConstructor();
constructor.setAccessible(true); //因为构造函数是私有的,所有设置权限
StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
StaticInnerClassSingleton newInstance = (StaticInnerClassSingleton) constructor.newInstance();
System.out.println(instance);
System.out.println(newInstance);
System.out.println(instance == newInstance);
}
}
对于懒汉式的单例,由于反射可以修改成员属性的值,所有不管构造方法多么的复杂,还是不能避免反射攻击,导致可以通过反射调用构造方法实例化对象。
Enum枚举单例、原理源码解析以及反编译实战(不理解)
这个暂时还不理解,先记录一下吧,等以后再研究。
容器单例
public class ContainerSingleton {
private ContainerSingleton(){
}
// HashMap 不是线程安全的,可以换成 ConcurrentHashMap
private static Map<String,Object> singletonMap = new HashMap<String,Object>();
public static void putInstance(String key,Object instance){
if(StringUtils.isNotBlank(key) && instance != null){
if(!singletonMap.containsKey(key)){
singletonMap.put(key,instance);
}
}
}
public static Object getInstance(String key){
return singletonMap.get(key);
}
}
public class T implements Runnable {
@Override
public void run() {
ContainerSingleton.putInstance("object",new Object());
Object instance = ContainerSingleton.getInstance("object");
System.out.println(Thread.currentThread().getName()+" "+instance);
}
}
// 测试函数
public class Test {
public static void main(String[] args) {
Thread t1 = new Thread(new T());
Thread t2 = new Thread(new T());
t1.start();
t2.start();
System.out.println("program end");
}
}
基于ThreadLocal 的 线程单例
public class ThreadLocalInstance {
private static final ThreadLocal<ThreadLocalInstance> threadLocalInstanceThreadLocal
= new ThreadLocal<ThreadLocalInstance>(){
@Override
protected ThreadLocalInstance initialValue() {
return new ThreadLocalInstance();
}
};
private ThreadLocalInstance(){
}
public static ThreadLocalInstance getInstance(){
return threadLocalInstanceThreadLocal.get();
}
}
public class T implements Runnable {
@Override
public void run() {
ThreadLocalInstance instance = ThreadLocalInstance.getInstance();
System.out.println(Thread.currentThread().getName()+" "+instance);
}
}
// 测试代码
public class Test {
public static void main(String[] args) {
System.out.println("main thread"+ThreadLocalInstance.getInstance());
Thread t1 = new Thread(new T());
Thread t2 = new Thread(new T());
t1.start();
t2.start();
System.out.println("program end");
}
}
源码举例
jdk 的 Runtime 类,属于饿汉式;
Spring 框架中的 bean 的单例是基于容器的,保证每个容器下的单例唯一。如 AbstractFactoryBean 类中的 getObject() 方法。
mybatis 中的 ErrorContext 类中的 instance() 方法,属于 ThreadLocal 的 基于线程的单例。
参考:Java设计模式透析之 —— 单例(Singleton),这个文章很适合初级,告诉我们单例是一步步如何来的,但是有两个问题,一个是 没有volatile关键字防止指令重排序,第二个是没有提到序列化和反序列化会导致单例对象不一致的问题。