应用场景:是指确保一个类在任何情况下都绝对只有一个实例(实现:隐藏其所有的构造方法),并提供一个全局访问点(实现:getInstance方法)
源码应用点:ApplicationContext\servletContext\DBPool
单例可以分为四类:
1、饿汉式单例
2、懒汉式单例
3、注册式单例(其中最优雅的写法:枚举式单例)
4、ThreadLocal单例
private Object readResolve(return INSTANCE);
加上这个方法可以防止序列号和反序列化破坏单例(原因:反序列化获取的对象默认是readResolve通过浅拷贝,拷贝的是对象的引用,所以还是同一个实例对象)
ThreadLocal单例:在同一个线程内会的实例是相同的,ThreadLocal中有个重要对象ThreadLocalMap,它的key是this就是当前线程,纬度是一线程为单位
单例模式的缺点:没有接口扩展困难(违背开闭原则的)
重点总结:
1、私有化构造器
2、保证线程安全
3、延迟加载
4、防止序列化和反序列化破坏单例
5、防止反射破坏单例
1、饿汉式写法一
/**
* 1、饿汉式写法一:类加载时就初始化
* 优点:执行效率高,性能高(没有加任何锁)
* 缺点:内存浪费(在某些情况下:在单例大量使用的情况);在类的加载时才初始化
* 会保证内存利用率高
*/
public class HungrySigleton {
/**
* 类加载时就初始化
*/
private static final HungrySigleton hungrySigleton = new HungrySigleton();
//构造函数私有化
private HungrySigleton(){
}
//提供一个全局访问点
public static HungrySigleton getHungrySigleton() {
return hungrySigleton;
}
}
2、饿汉式单例写法二
/**
* 2、饿汉式单例写法二:
*/
public class HungryStaticSingleton {
/**
* 类加载顺序:
* //先静态后动态
* //先上,后下
* //先属性,后方法
*/
private static final HungryStaticSingleton hungryStaticSingleton;
/**
* 使用静态代码块复制,和写法一基本类似
*/
static {
hungryStaticSingleton = new HungryStaticSingleton();
}
public static HungryStaticSingleton getHungryStaticSingleton() {
return hungryStaticSingleton;
}
}
3、懒汉模式:外部需要时才实例化
/**
* 3、懒汉模式:外部需要时才实例化
* 优点:节省内存(需要时才实例化)
* 缺点:线程不安全(多线程调用getInstance方法,有可能创建多个实例,单例失败)
*/
public class LazySimpleSingleton {
//声明一个LazySimpleSingleton对象自己的全局变量
private LazySimpleSingleton instance;
//构造函数私有化
private LazySimpleSingleton(){
}
//提供一个全局访问点
public LazySimpleSingleton getInstance(){
if (instance == null){
instance = new LazySimpleSingleton();
}
return instance;
}
//例如两个线程同时调用getInstance,运行结果:
//出现一个实例:1、正常顺序执行,2、后者覆盖前者情况
//出现两个实例:同时进入条件,按顺序发返回
//多线程调试:先断点,右击断点选择thread,执行方法,选择控制台Debugger下Frames,查看线程各个线程,选择执行哪个线程(
// 线程执行顺序可以随意控制),
}
/**
* 4、
* 优点:节省了内存,线程安全
* 缺点:加锁造成了性能瓶颈
*/
public class LazySimpleLockSingleton {
//声明一个LazySimpleSingleton对象自己的全局变量
private LazySimpleLockSingleton instance;
//构造函数私有化
private LazySimpleLockSingleton(){
}
//提供一个全局访问点
public synchronized LazySimpleLockSingleton getInstance(){
//java8之后可以用juc的重入锁
//Lock lock = new ReentrantLock();
//lock.lock();
if (instance == null){
instance = new LazySimpleLockSingleton();
}
//lock.unlock();
return instance;
}
//例如两个线程同时调用getInstance,运行结果:
//出现一个实例:1、正常顺序执行,2、后者覆盖前者情况
//出现两个实例:同时进入条件,按顺序发返回
//多线程调试:先断点,右击断点选择thread,执行方法,选择控制台Debugger下Frames,查看线程各个线程,选择执行哪个线程(
// 线程执行顺序可以随意控制),
}
5、双重检查
/**
* 5、双重检查
* 优点:节省了内存,线程安全,性能高了
* 缺点:可读性不好,不够优雅
*/
public class LazyDoubleSimpleSingleton {
//声明一个LazySimpleSingleton对象自己的全局变量
private volatile LazyDoubleSimpleSingleton instance;
//构造函数私有化
private LazyDoubleSimpleSingleton(){
}
//提供一个全局访问点
public LazyDoubleSimpleSingleton getInstance(){
//第一次检查是否需要阻塞
if (instance == null) {
synchronized (this) {
//第二次检查是否需要阻塞
if (instance == null) {
instance = new LazyDoubleSimpleSingleton();
//多线程情况下,有指令重排序问题(需要在声明全局变量的时候加上volatile)
}
}
}
return instance;
}
}
6、
/**
* 6
* classPath:LazyStaticInnerClassSingleton.class(1)
* LazyStaticInnerClassSingleton$LazyHolder.class(2)
* 加载类1时不会加载类2,只用当外部使用到getInstance时才会触发加载类2
* 优点:写法优雅,利用了Java本身的语法特点(性能高,避免了内存浪费,不会有指令重拍问题)
* 缺点:能够被反射破坏(前面5个也能被反射破坏,为什么:前面六种都是私有化构造函数,但是反射能够从外部拿到类的私有化构造
* 通过设置构造方法setAccessible为true,强制访问私有构造方法)
*/
public class LazyStaticInnerClassSingleton {
private LazyStaticInnerClassSingleton(){
//改进方法
if (LazyHolder.INSTANCE != null) {
throw new RuntimeException("不允许非法访问");
}
}
private static LazyStaticInnerClassSingleton getInstance(){
return LazyHolder.INSTANCE;
}
//静态内部类(静态变量在加载的时候就会被分配内存空间,静态内部类不一样,只在使用类时才分配内存)
private static class LazyHolder{
private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton();
}
}
/**7、
* 枚举式单例(最优雅的写法)
* 不能用反射创建枚举对象
* 内存浪费(在某些情况下:在单例大量使用的情况)
*/
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;
}
}
网友评论