单例设计模式
目的:保证一个类仅有一个实例,并提供一个全局的访问点。
类型:创建型
使用场景:确保任何情况下绝对仅有一个实例(线程池、数据库连接池)
优点:
- 仅有一个实例,减少内存开销
- 避免对资源文件多重占用严格控制访问
缺点:
- 无接口
- 拓展困难
重点:
- 私有构造器
- 线程安全
- 延迟加载
- 序列化和反序列化安全
- 反射攻击
单例模式:懒汉式、饿汉式、静态内部类、枚举
懒汉式
public class LazySingleton {
/**
* 初始化懒加载
*/
private static LazySingleton lazySingleton = null;
/**
* 私有构造方法
*/
private LazySingleton(){
}
/**
* 静态方法public方法,获得实例
* @return lazySingleton
*/
public static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
单线程可用,但多线程会破坏单例契约,线程不安全
public class LazySingletonTest {
/**
* 检测线程是否安全
*/
public static void main( String[] args ) {
// LazySingleton lazySingleton = new LazySingleton(); 私有构造方法无法创建实例
Thread t1 = new Thread( new T() );
Thread t2 = new Thread( new T() );
t1.start();
t2.start();
System.out.println("Program End...");
}
}
public class T implements Runnable {
@Override
public void run() {
LazySingleton lazySingleton = LazySingleton.getInstance();
System.out.println(Thread.currentThread().getName()+ " "+lazySingleton);
}
}
Debug调试,令两个线程都溜入该单例模式的20行中,闷声发大财。两个线程均产生各自的对象
Thread-0 singleton.LazySingleton@15082754
Thread-1 singleton.LazySingleton@16fea746
Program End...
改进:在方法中添加synchronized,锁定整个类,当一个线程创建单例时,会阻塞其他线程进入该类
synchronized:
- 加在到静态方法中,锁定整个class文件
- 加在非静态方法中,锁的是堆内存中的对象
public synchronized static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
public static LazySingleton getInstance(){
synchronized(LazySingleton.class){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
}
return lazySingleton;
}
懒汉式进化—Double Check Lazy Singleton
public class LazyDoubleCheckSingleton {
/**
* volatile解决由重排序而产生的空指针异常,volatile中不允许重排序
*/
private static volatile LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
private LazyDoubleCheckSingleton(){
}
public static LazyDoubleCheckSingleton getInstance(){
if(lazyDoubleCheckSingleton == null){
synchronized ( LazyDoubleCheckSingleton.class ){
if(lazyDoubleCheckSingleton == null){
/*
* 1.给对象分配内存
* 2.初始化对象
* 3.设置lazyDoubleCheckSingleton指向刚分配的内存地址
* 所有线程在执行java程序时必须要遵守intra-thread semantics
* 保证重排序不会改变单线程内的排序结果
*
*/
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
}
}
}
return lazyDoubleCheckSingleton;
}
}
性能优化:
- 添加判断,减少synchronize锁的使用
- volatile修饰变量,解决由重排序而引起的空指针异常,解决方式是不允许重排序
静态内部类
public class StaticInnerClassSingleton {
/**
* 私有构造方法
*/
private StaticInnerClassSingleton(){
}
/**
* 静态内部类
*/
private static class InnerClass{
private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();
}
/**
* 获取方法
* @return Instance
*/
public static StaticInnerClassSingleton getInstance(){
return InnerClass.staticInnerClassSingleton;
}
}
原理:
- 类初始化时将获得初始化锁,使得对象初始化的过程(重排序)对其他线程不可见。
- 基于类初始化的延迟单例解决方案
饿汉式
饿汉式单例模式:类加载时就初始化完成,可以用final修饰,可用静态代码块的方式创建实例
public class HungrySingleton {
/**
* 创建实例,直接初始化
*/
// private static final HungrySingleton hungrySingleton = new HungrySingleton();
private static final HungrySingleton hungrySingleton;
static {
hungrySingleton = new HungrySingleton();
}
/**
* 构造方法私有
*/
private HungrySingleton(){
}
/**
* 单例方法
*/
public static HungrySingleton getInstance(){
return hungrySingleton;
}
}
- 浪费资源
- 线程安全
枚举类
序列化和反序列化破坏单例模式
反射破坏单例模式:无参构造方法的反射破坏——懒汉式无法防御反射防御