持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第15天
1. 彻底玩转单例模式
饿汉式、DCL懒汉式
饿汉式
//饿汉式单例模式
public class Hungry {
//浪费空间
private byte[] date1 = new byte[1024*1024];
private byte[] date2 = new byte[1024*1024];
private byte[] date3 = new byte[1024*1024];
private byte[] date4 = new byte[1024*1024];
private byte[] date5 = new byte[1024*1024];
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式
- 第一种实现方式(不推荐)
private static Singleton1 singleton;
private Singleton1() {
}
public static Singleton1 getInstance() {
if(singleton==null)
{
singleton=new Singleton1();
}
return singleton;
}
} 缺点:只能在单线程下使用,多线程下会产生多个对象。
-
第二种实现方式(不推荐)
public class Singleton2 { private static Singleton2 singleton; private Singleton2() { } public static synchronized Singleton2 getInstance() { if(singleton==null) { singleton=new Singleton2(); } return singleton; } }缺点:加锁进行同步,虽然可以保证单例,但效率太低,浪费大量时间。
-
第三种实现方式(推荐) 著名的双重检查机制
public class Singleton3 {
private volatile static Singleton3 singleton;
//private static Singleton singleton;
private Singleton3() {
}
public static Singleton3 getInstance() {
if(singleton==null) {
synchronized (Singleton3.class) {
if(singleton==null) {
singleton=new Singleton3();
}
}
}
return singleton;
}
}
优点:保证单例的同时,也提高了效率。
注意:singleton前面要加volatile关键字来保证程序运行的有序性,否则多线程访问下可能会出现对象未初始化错误!
说明:在内存中,创建一个变量需要三步:1.申请一块内存 ;2.调用构造方法初始化 ;3 分配一个指针指向这块内存
在编译原理中,有一个重要的内容叫做编译器优化,即在不改变原来语义的情况下,调整语句的执行顺序,来让程序运行的更快
因此存在这样一种情况,有两个线程A、B同时访问getInstance方法
A线程判断对象为空,没来得及进行第二次判断,(时间片用完了,B线程进入) B线程判断对象为空,执行创建变量的3步,先申请一块内存,后分配一个指针指向这块内存,但还没有进行初始化(时间片用完了,A线程进入) A线程接着执行,发现此时singleton已经不为空了,所以直接返回,但此时返回的singleton对象虽然B线程已经new了,但还没有初始化这个实例并没有构造完成,此时如果A线程使用这个实例,程序就会出现对象未初始化错误了。
//懒汉式单例模式
public class LazyMan {
private static boolean key = false;
private LazyMan(){
synchronized (LazyMan.class){
if (key==false){
key=true;
}
else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
System.out.println(Thread.currentThread().getName()+" ok");
}
private volatile static LazyMan lazyMan;
//双重检测锁模式 简称DCL懒汉式
public static LazyMan getInstance(){
//需要加锁
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
/**
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 就有可能出现指令重排问题
* 比如执行的顺序是1 3 2 等
* 我们就可以添加volatile保证指令重排问题
*/
}
}
}
return lazyMan;
}
//单线程下 是ok的
//但是如果是并发的
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//Java中有反射
// LazyMan instance = LazyMan.getInstance();
Field key = LazyMan.class.getDeclaredField("key");
key.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); //无视了私有的构造器
LazyMan lazyMan1 = declaredConstructor.newInstance();
key.set(lazyMan1,false);
LazyMan instance = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(lazyMan1);
System.out.println(instance == lazyMan1);
}
}
静态内部类
//静态内部类
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.holder;
}
public static class InnerClass{
private static final Holder holder = new Holder();
}
}
单例不安全, 因为反射
枚举
//enum 是什么? enum本身就是一个Class 类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
//java.lang.NoSuchMethodException: com.ogj.single.EnumSingle.<init>()
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
使用枚举,我们就可以防止反射破坏了。
枚举类型使用JAD最终反编译后源码:
如果我们看idea 的文件:会发现idea骗了我们,居然告诉我们是有有参构造的,我们使用jad进行反编译。
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}