开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第16天,点击查看活动详情
本文主要介绍JUC中的单例模式,以及饿汉式单例、懒汉式单例的一些改进与问题,以及最后提到了枚举单例。
19. 单例模式JUC
单例:构造器私有,别人无法new
饿汉式单例
//饿汉式单例:来个对象就接收
public class Hungry {
// 可能会浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
//构造器私有,别人无法new
private Hungry(){
}
//先new一个对象(保证唯一)
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
针对饿汉式单例不好的地方就是,来一个对象就接受。
懒汉式单例
下面这个是普通的懒汉式单例,懒汉式单例不会来一个对象就接收,他是在用的时候再加载。懒汉式单例在单线程的情况下是ok的,是单例的。但是在多线程的情况下,不见得就是单例的。根据这段程序的运行结果也知道它不是单例了。
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName() + "ok");
}
private LazyMan lazyMan;
//懒汉式单例
public static LazyMan getInstance() {
if(lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
//单线程下确实单例ok
//多线程并发(单例不是单例,出现问题,要加锁,上面已经加锁)
public static void main(String[] args) {
for (int i = 1; i <=10 ; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
运行结果
Thread-0ok
Thread-1ok
针对上述情况的解决方式就是DCL懒汉式,双重检测锁模式的懒汉式单例。什么意思呢?就是需要针对下面这个方法进行修改如下:
- 加锁验证:运行结果确实是单例了。但是这种在极端情况下也会有问题,原因就是
lazyMan = new LazyMan();不是个原子性操作。非原子性会执行三步:1。分配内存空间2。执行构造方法,初始化对象3。把这个对象指向这个空间(可能发生指令重排),解决方式就是加一个volatile。
public static LazyMan getInstance() {
if(lazyMan == null){
synchronized (LazyMan.class){
if(lazyMan == null){
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
private volatile static LazyMan lazyMan;
当然到这里也不是安全的,java反射就可以破坏这个单例。比如把测试代码改成这样:
public static void main(String[] args) throws Exception {
LazyMan instance = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
//单例又被破坏了
LazyMan instance1 = declaredConstructor.newInstance();
System.out.println(instance);//com.Juc.single.LazyMan@232204a1
System.out.println(instance1);//com.Juc.single.LazyMan@4aa298b7
}
程序运行结果:
com.Juc.single.LazyMan@232204a1
com.Juc.single.LazyMan@4aa298b7
很明显反射破坏了单例。解决方法就是在私有构造那里上锁:
private LazyMan(){
//解决反射破坏单例
synchronized (LazyMan.class){
if(lazyMan!=null){
throw new RuntimeException("不要试用反射破坏异常");
}
}
// System.out.println(Thread.currentThread().getName() + "ok");
}
虽然解决了,但是改成下面这样又破坏了单例:
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
//单例又被破坏了
LazyMan instance1 = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);//com.Juc.single.LazyMan@232204a1
System.out.println(instance2);//com.Juc.single.LazyMan@4aa298b7
就要用到枚举了,枚举的这个一定是单例;
//enum 本身是一个class类,jdk1。5版本就有
public enum EnumSingle {
INSTANCE;//像这样一个对象默认就是单例,不可能被拿走
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
EnumSingle instance1 = EnumSingle.INSTANCE;
EnumSingle instance2 = EnumSingle.INSTANCE;
// Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance3 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance3);
}
}