JUC - 多线程之 单例模式
Posted MinggeQingchun
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了JUC - 多线程之 单例模式相关的知识,希望对你有一定的参考价值。
单例模式(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 反编译得到 有参构造
直接报错
单例模式分为懒汉式、饿汉式、饿汉式同步锁、双重校验锁、静态内部类、枚举类,每一种都有自己的优缺点,可以根据自己的项目实际需要选择适合的单例模式
以上是关于JUC - 多线程之 单例模式的主要内容,如果未能解决你的问题,请参考以下文章
JUC - 多线程之Synchronized和Lock锁;生产者消费者模式
JUC并发编程 共享模式之工具 ThreadPoolExecutor 多线程设计模式 -- 异步模式之工作线程(定义饥饿 & 解决饥饿 & 线程池创建多少线程数目合适)