1 单例模式
单例模式是java中最简单的设计模式之一 ,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。这种设计模式涉及到一个单一的类,该类主要是用来创建单一的对象,同时提供给客户唯一的访问该对象的方式/方法,想要使用这个对象,直接访问该方法即可。
1.1 单例模式结构(单例模式主要角色
- 单例类:仅创建一个实例的类
- 访问类:使用单例类(不是必须)
1.2 单例模式的实现
单例设计模式分类两种:
- 饿汉式:类加载时就会导致该单实例对象被创建
- 懒汉式:类加载不会导致该单实例被创建,而是首次使用该单例时才创建
1、饿汉式-方式1(静态变量的方式)
public class Singleton{
//私有化构造方法
private Singleton(){}
//在成员变量位置创建唯一实例
private static Singleton instance = new Singleton();
//对外提供唯一的访问途径
public static Singleton getInstance(){
return instance;
}
}
//备注:这种方式有一个缺点:当该类被加载时,对象就创建好了,如果对象比较大且不被使用,将会浪费内存
2、饿汉式-方式2(静态代码块方式)
public class Singleton{
//私有构造方法
private Singleton(){}
//定义成员变量,是一个引用(不创建对象)
private static Singleton instance;
static{
instance = new Singleton();
}
//提供唯一的访问该对象方式
public stataic Singleton getInstance(){
return instance;
}
}
//缺点和饿汉式-方式1一致
3.懒汉式-方式1(线程不安全)
public class Singleton{
//私有构造方法
private Singleton(){}
//在成员变量位置定义该类的引用
private static Singleton instance;
//对外提供静态的唯一访问方式
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
//当调用getInstance方法时才会创建对象赋值给instance变量,所以不会浪费内存,但在多线程环境下会出现安全隐患
4、懒汉式-方式2(线程安全)
//给访问入口加一synchronized
public class Singleton{
//私有构造方法
private Singleton(){}
//在成员变量位置定义该类的引用
private static Singleton instance;
//对外提供静态的唯一访问方式
public static synchronized Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
//缺点:虽然解决了多线程下的安全问题,但其是用来synchronized这个重量级锁来同步线程,导致效率比较低,因为synchronized的监视器是依赖与操作系统底层的,而操作系统底层线程的切换时比较复杂的:首先要从用户态切换到内核态,这个过程中间要保存很多信息,需要时间,而且当前synchronized锁的是类,整个方法只能由单个线程调用,综上效率低。
5、懒汉式-方式3(双重检查锁)
public class Singleton{
//私有构造方法
private Singleton(){}
//在成员变量位置定义该类的引用
private static Singleton instance;
//对外提供静态的唯一访问方式
public static Singleton getInstance(){
//第一次判断,如果instance不为null,不进入枪锁阶段,直接返回实例
if(instance == null){
synchronized (Singleton.class){
//抢到锁之后再判断是否为null
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
//缺点:多线程情况下,可能会出现空指针情况,这是因为jvm在实例化对象的时候会进行优化和指令重排序操作,为了防止指令重排,给成员变量加上 volatile 关键字
6、懒汉式-方式4(静态内部类方式)
静态内部类单例模式中实例由内部类创建,由于jvm在加载外部类的过程中,是不会加载内部类的,只有内部类的属性/方法被调用时才会别加载,并初始化其静态属性。静态属性由于被static修饰,保证只被实例化一次,并且严格保证实例化顺序。
public class Singleton{
//私有构造方法
private Singleton(){}
//定义静态内部类
private static class SingletonHolder{
private static final Singleton INSTANCE = new Singleton();
}
//访问入口
public static Singleton getInstance(){
return SingletonHolder.INSTANCE ;
}
}
//优点:只有当第一次调用入口方法时,静态内部类才被加载且初始化,对象被创建且仅一次创建,在多线程环境下,无需加锁就解决安全问题,并且没有浪费任何内存
7、饿汉式(枚举方式)
public enum Singleton{
INSTANCE;
}
//优点:枚举方式属于饿汉式方式,且是所有单例模式中的唯一一个不被破坏的单例实现模式
1.3 破化单例模式方式
- 序列化和反序列化
- 反射
通过序列化和反序列化以及反射这两种方式来破坏单例,可以创建多个实例对象。
1.3.1 反射方式破坏单例的解决办法
思路:反射破坏是通过取消访问检查,并获取构造方法来创建实例。只需要在构造方法里写判非空抛出异常的代码,即当通过反射活动构造器来创建实例时,每次都判断实例是否已经存在,存在就抛出异常不让它继续创建。
//构造方法添加异常抛出
private Singleton(){
if(instance !=null){
throw new RuntimeException();
}
}