单例模式(Singleton Pattern)是一种非常简单的设计模式之一,当我们使用的对象要在全局唯一时就需要用到该模式,以保证对象的唯一性。除此之外,还能避免反复的实例化对象,减少内存开销
单例类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象
单例主要有如下创建方式
一、饿汉式--静态变量(线程安全)
/**
* 1、饿汉式-静态变量(线程安全)
*/
public class Hungry1 {
// 可能会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
// 1、将构造方法私有化,外部无法使用new构造方法创建实例
private Hungry1(){
}
// 2、内部创建对象
private static Hungry1 singleton = new Hungry1();
// 3、对外获取实例的方法
public static Hungry1 getInstance(){
return singleton;
}
}
优点:写法简单;避免了线程同步问题
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading懒加载的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费
二、饿汉式--静态代码块(线程安全)
/**
* 2、饿汉式-静态代码块(线程安全)
*/
public class Hungry2 {
// 可能会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
// 1、将构造方法私有化,外部无法使用new构造方法创建实例
private Hungry2(){
}
// 2、内部静态代码块中创建对象
private static Hungry2 singleton;
static{
singleton = new Hungry2();
}
// 3、对外获取实例的方法
public static Hungry2 getInstance(){
return singleton;
}
}
优缺点同上
三、懒汉式--简单判断非空(多线程并发不安全,单线程无影响)
/**
* 3、懒汉式-简单判断非空(多线程并发不安全,单线程无影响)
*/
public class Lazy1 {
// 1、将构造方法私有化,外部无法使用new构造方法创建实例
private Lazy1() {}
// 2、声明类成员变量singleton
private static Lazy1 singleton;
// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
public static Lazy1 getInstance(){
if (singleton == null){
singleton = new Lazy1();
}
return singleton;
}
}
优点:在使用时才会生成对象,能够减少内存开销
缺点:线程不安全,只适用单线程,当有多个线程访问时,能够产生多个对象,不满足单例模式的要求
在多线程下,一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
四、懒汉式--synchronized锁方法(线程安全,效率低)
/**
* 4、懒汉式-synchronized锁方法(线程安全,效率低)
*/
public class Lazy2 {
// 1、将构造方法私有化,外部无法使用new构造方法创建实例
private Lazy2() {}
// 2、声明类成员变量singleton
private static Lazy2 singleton;
// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
public static synchronized Lazy2 getInstance(){
if (singleton == null){
singleton = new Lazy2();
}
return singleton;
}
}
优点:线程安全
缺点:效率太低**,**synchronized锁了整个方法,下一个线程必须等上一个线程释放锁,效率很低,不建议使用
五、懒汉式--synchronized同步代码块(多线程并发不安全)
/**
* 5、懒汉式-synchronized同步代码块(多线程并发不安全)
*/
public class Lazy3 {
// 1、将构造方法私有化,外部无法使用new构造方法创建实例
private Lazy3() {}
// 2、声明类成员变量singleton
private static Lazy3 singleton;
// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
public static synchronized Lazy3 getInstance(){
if (singleton == null){
synchronized (Lazy3.class){
singleton = new Lazy3();
}
}
return singleton;
}
}
优点:在使用时才会生成对象,能够减少内存开销
缺点:线程不安全
假如一个线程进入了if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例
六、懒汉式--双重检查DCL(线程安全;常用)
/**
* 6、懒汉式-双重检查(线程安全;常用)
*/
public class Lazy4 {
// 1、将构造方法私有化,外部无法使用new构造方法创建实例
private Lazy4() {}
// 2、声明类成员变量singleton
private volatile static Lazy4 singleton;
// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
public static Lazy4 getInstance(){
if (singleton == null){
synchronized (Lazy4.class){
if (singleton == null){
/* 有可能 非原子性操作
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 123
* 132 ;此时lazyMan还没有完成构造,报出空指针异常,最好加上volatile修饰
*/
singleton = new Lazy4();
}
}
}
return singleton;
}
}
Double-Check概念对于多线程开发者来说不会陌生,如代码中所示,我们进行了两次if (singleton == null)检查,这样就可以保证线程安全了。这样,实例化代码只用执行一次,后面再次访问时,判断if (singleton == null),直接return实例化对象
优点:效率高,线程安全
缺点:可通过反射破坏单例模式
反射破坏双重懒汉式单例
1、反射破坏双重懒汉式单例--破坏私有无参构造器
/**
* 6、懒汉式-双重检查(线程安全;常用)
*
* 1、反射破坏双重懒汉式单例--破坏私有无参构造器
*/
public class Lazy4Reflect {
// 1、将构造方法私有化,外部无法使用new构造方法创建实例
private Lazy4Reflect() {}
// 2、声明类成员变量singleton
private static Lazy4Reflect singleton;
//private static boolean isReflect = false;
// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
public static Lazy4Reflect getInstance(){
if (singleton == null){
synchronized (Lazy4Reflect.class){
if (singleton == null){
/* 有可能 非原子性操作
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 123
* 132 ;此时lazyMan还没有完成构造,报出空指针异常
*/
singleton = new Lazy4Reflect();
}
}
}
return singleton;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 反射破坏单例
Lazy4Reflect instance = Lazy4Reflect.getInstance();
Constructor<Lazy4Reflect> constructor = Lazy4Reflect.class.getDeclaredConstructor(null);
// 忽略私有构造器
constructor.setAccessible(true);
Lazy4Reflect instance1 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
输出如下,两个 instance内存地址明显不同
2、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁也破坏
/**
* 6、懒汉式-双重检查(线程安全;常用)
*
* 2、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁也破坏
*/
public class Lazy4Reflect1 {
// 1、将构造方法私有化,外部无法使用new构造方法创建实例
private Lazy4Reflect1() {
synchronized (Lazy4Reflect1.class){
if (singleton != null){
throw new RuntimeException("不要使用反射破坏异常");
}
}
}
// 2、声明类成员变量singleton
private static Lazy4Reflect1 singleton;
//private static boolean isReflect = false;
// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
public static Lazy4Reflect1 getInstance(){
if (singleton == null){
synchronized (Lazy4Reflect1.class){
if (singleton == null){
/* 有可能 非原子性操作
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 123
* 132 ;此时lazyMan还没有完成构造,报出空指针异常
*/
singleton = new Lazy4Reflect1();
}
}
}
return singleton;
}
}
class Test1 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 反射破坏单例
//Lazy4Reflect1 instance = Lazy4Reflect1.getInstance();
Constructor<Lazy4Reflect1> constructor = Lazy4Reflect1.class.getDeclaredConstructor(null);
// 忽略私有构造器
constructor.setAccessible(true);
// 两个对象都不经过 getInstance()创建,反射拿到
Lazy4Reflect1 instance = constructor.newInstance();
Lazy4Reflect1 instance1 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
3、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁,加标志位也破坏
/**
* 6、懒汉式-双重检查(线程安全;常用)
*
* 3、反射破坏双重懒汉式单例--破坏私有无参构造器,加锁,加标志位也破坏
*/
public class Lazy4Reflect2 {
// 1、将构造方法私有化,外部无法使用new构造方法创建实例
private Lazy4Reflect2() {
synchronized (Lazy4Reflect2.class){
if (isReflect = false){
isReflect = true;
}else {
throw new RuntimeException("不要使用反射破坏异常");
}
}
}
// 2、声明类成员变量singleton
private static Lazy4Reflect2 singleton;
private static boolean isReflect = false;
// 3、对外获取实例的方法,先判断singleton是否为空,为空则创建
public static Lazy4Reflect2 getInstance(){
if (singleton == null){
synchronized (Lazy4Reflect2.class){
if (singleton == null){
/* 有可能 非原子性操作
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 123
* 132 ;此时lazyMan还没有完成构造,报出空指针异常
*/
singleton = new Lazy4Reflect2();
}
}
}
return singleton;
}
}
class Test2 {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchFieldException {
// 反射破坏单例
Field isReflectField = Lazy4Reflect2.class.getDeclaredField("isReflect");
isReflectField.setAccessible(true);
//Lazy4Reflect1 instance = Lazy4Reflect1.getInstance();
Constructor<Lazy4Reflect2> constructor = Lazy4Reflect2.class.getDeclaredConstructor(null);
// 忽略私有构造器
constructor.setAccessible(true);
// 两个对象都不经过 getInstance()创建,反射拿到
Lazy4Reflect2 instance = constructor.newInstance();
isReflectField.set(instance,false);
Lazy4Reflect2 instance1 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
七、静态内部类(线程安全;常用)
/**
* 7、静态内部类(线程安全;常用)
*/
public class StaticInnerClass {
// 1、将构造方法私有化,外部无法使用new
private StaticInnerClass(){
}
// 2、一个静态内部类 创建实例
private static class StaticInstance{
private static final StaticInnerClass INSTENCE = new StaticInnerClass();
}
// 3、直接调用静态内部类,返回instance
public static StaticInnerClass getInstance(){
return StaticInstance.INSTENCE;
}
}
这种方式跟饿汉式方式采用的机制类似,但又有不同。
两者都是采用了类装载的机制来保证初始化实例时只有一个线程。
不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化
类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的
优点:线程安全,延迟加载,效率高
缺点:可通过反射破坏单例模式
反射破坏
/**
* 7、静态内部类(线程安全;常用)
*
* 可以通过反射破坏
*/
public class StaticInnerClass {
// 1、将构造方法私有化,外部无法使用new
private StaticInnerClass(){
}
// 2、一个静态内部类 创建实例
private static class StaticInstance{
private static final StaticInnerClass INSTENCE = new StaticInnerClass();
}
// 3、直接调用静态内部类,返回instance
public static StaticInnerClass getInstance(){
return StaticInstance.INSTENCE;
}
}
class TestStaticInner{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 反射破坏单例
StaticInnerClass instance = StaticInnerClass.getInstance();
Constructor<StaticInnerClass> constructor = StaticInnerClass.class.getDeclaredConstructor(null);
// 忽略私有构造器
constructor.setAccessible(true);
StaticInnerClass instance1 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
八、枚举(线程安全;可防止反序列化;强烈推荐!!!)
/**
* 8、枚举(线程安全;可防止反序列化;强烈推荐!!!)
*/
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
优点:线程安全,不用担心反射破坏单例模式
缺点:枚举类占用内存多
构造器源码
public final class Constructor<T> extends Executable {
@CallerSensitive
public T newInstance(Object ... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException
{
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
ConstructorAccessor ca = constructorAccessor; // read volatile
if (ca == null) {
ca = acquireConstructorAccessor();
}
@SuppressWarnings("unchecked")
T inst = (T) ca.newInstance(initargs);
return inst;
}
}
尝试使用反射破坏
/**
* 8、枚举(线程安全;可防止反序列化;强烈推荐!!!)
*/
public enum EnumSingleton {
INSTANCE;
public EnumSingleton getInstance(){
return INSTANCE;
}
}
class TestEnumSingleton{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// 反射破坏单例
EnumSingleton instance = EnumSingleton.INSTANCE;
// java.lang.NoSuchMethodException
//Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(null);
// 注:通过 jad 反编译得到 有参构造
Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class,int.class);
// 忽略私有构造器
constructor.setAccessible(true);
// java.lang.NoSuchMethodException
EnumSingleton instance1 = constructor.newInstance();
System.out.println(instance);
System.out.println(instance1);
}
}
通过jad 反编译得到 有参构造
直接报错
单例模式分为懒汉式、饿汉式、饿汉式同步锁、双重校验锁、静态内部类、枚举类,每一种都有自己的优缺点,可以根据自己的项目实际需要选择适合的单例模式