什么是单例
确保任何情况下都绝对只有一个实例
ServletContext、ServletConfig、ApplicationContext、DBpool
任何情况下包括:多线程、并发、反射调用构造器
官方:是指确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点
隐藏其所有的构造方法
属于创建型模式
写法:
懒汉式
饿汉式
注册式
ThreadLocal单例
枚举、double check、内部类
目的:
为什么要这么写?
饿汉式:先吃饱再说
优点:线程安全,逻辑简单、空间换时间
缺点:如果这种方法对象被大量使用,不管用不用,都初始化,导致内存开销增加
package cn.single.hungry;
/**
* 写法大量使用,类本身的实例不管你用不用,默认就赋值
* 可能造成内存浪费
*/
/*
* 饿汉式
* */
public class HungrySingle {
//不加final有可能被误操作覆盖掉
private static final HungrySingle hungrySingleton;
static {
hungrySingleton = new HungrySingle();
}
private HungrySingle(){}
public static HungrySingle getInstance(){
return hungrySingleton;
}
}
懒汉式:要调用的用的时候吃(赋值)
简单写法:
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton(){
}
public static LazySingleton getInstance(){
//第一次使用的时候,他会做判断
if (null == instance){
instance = new LazySingleton();
}
//第二次就直接返回
return instance;
}
}
优点:资源占用少,逻辑简单
缺点:线程不安全(随机产生)
不安全原因
两个线程打印相同结果:
1.线程按顺序执行(正常)
2.线程同时进入(后者覆盖前者)
两个线程打印不同结果
线程同时进行(先后执行之后的逻辑)
解决方式
加锁
public class LazySingleton {
private static LazySingleton instance = null;
private LazySingleton(){
}
public static synchronized LazySingleton getInstance(){
//第一次使用的时候,他会做判断
if (null == instance){
instance = new LazySingleton();
}
//第二次就直接返回
return instance;
}
}
简单(synchronize)
缺点:效率低利用率低
doublecheck写法
private volatile static LazyDoubleChecksSingleton instance = null;
private LazyDoubleChecksSingleton(){
}
public static LazyDoubleChecksSingleton getInstance(){
if (instance ==null){
synchronized (LazyDoubleChecksSingleton.class){
//第一次使用的时候,他会做判断
if (null == instance){
instance = new LazyDoubleChecksSingleton();
//指令重排序的问题
}
}
}
//第二次就直接返回
return instance;
}
缺点:可读性下降,不够优雅
LazyinnerClassSingleton写法
优点:优雅,利用天生的语法优势
public class LazyinnerClassSingleton {
private LazyinnerClassSingleton(){
}
public static final LazyinnerClassSingleton getInstance(){
return LazyHolder.INSTANCE;
}
private static class LazyHolder{
private static final LazyinnerClassSingleton INSTANCE = new LazyinnerClassSingleton();
}
}
以上5种都有共同的缺点:
private Singleton(){}
它是骗人的,只按套路出牌
破解方法(反射)
public class RelectTest {
public static void main(String[] args) {
try {
Constructor c= LazyinnerClassSingleton.class.getDeclaredConstructor();
c.setAccessible(true);
Object instance = c.newInstance();
System.out.println(instance);
Object instance1 = c.newInstance();
System.out.println(instance1);
//返回结果
//cn.single.lazy.LazyinnerClassSingleton@4554617c
//cn.single.lazy.LazyinnerClassSingleton@74a14482
} catch (Exception e) {
e.printStackTrace();
}
}
}
注册式单例
枚举式
官方:有jdk为枚举保驾护航
/**
* 绝对信任线程安全
* 高性能写法
*/
//常量中去使用,常量不就是用来大家都能够共用吗?
//通常在通用API中使用
public enum EnumSingleton {
INSTANCE;
private Object data;
public Object getData(){
return data;
}
public void setData(Object data){
this.data = data;
}
public static EnumSingleton getInstance(){
return INSTANCE;
}
}
Spring IOC容器就是单例实现
容器式单例
默认值,类名首字母小写
比较实用的
总结:
7种写法
饿汉式
static块、非static块
懒汉式
simple在方法上加synchronized关键字
doublecheck写法
innerclass
注册式
enum(Effctive Java推荐,最优雅的写法)
Container容器,Spring IOC
单例并不是最优雅就最好
考虑实用性
两个(饿汉式)
最底层的代码对性能要求很高(enum,innderclass)
单例对象非常多,使用根据实际情况,(spring ioc)