单例模式

单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例,并 提供一个全局访问点。

饿汉式单例

饿汉式单例是在类加载的时候就立即初始化,并且创建单例对象。绝对线程安全,在线 程还没出现以前就是实例化了,不可能存在访问安全问题。
优点:没有加任何的锁、执行效率比较高,在用户体验上来说,比懒汉式更好。
缺点:类加载的时候就初始化,不管用与不用都占着空间,浪费了内存,有可能占着茅 坑不拉屎。

public class HungrySingleton {
    //static静态加载一次
    //final 防止再次实例化,反射
    private static final HungrySingleton hungrySingleton = new HungrySingleton();

    //private 外部无法创建
    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
        System.out.println("hello world");
        return hungrySingleton;
    }
}

或者静态代码块的写法

public class HungrySingleton {
    //static静态加载一次
    //final 防止再次实例化,反射
    private static final HungrySingleton hungrySingleton;
	static{
		hungrySingleton = new HungrySingleton();
	}
    //private 外部无法创建
    private HungrySingleton(){}

    public static HungrySingleton getInstance(){
        System.out.println("hello world");
        return hungrySingleton;
    }
}

思考:final能不能不加 ?

public class Test2 {
    public static void main(String[] args) throws Exception {

        Class<HungrySingleton> clazz = HungrySingleton.class;
        Constructor c = clazz.getDeclaredConstructor();
        c.setAccessible(true);
        HungrySingleton a  = (HungrySingleton) c.newInstance();
        System.out.println("反射创建出来的对象"+a);

        Field[] files = clazz.getDeclaredFields();
        for (Field file : files) {
            file.setAccessible(true);
            HungrySingleton singleton1 = (HungrySingleton) file.get(HungrySingleton.getInstance());
            System.out.println("拿到单例模式创建的对象: " + singleton1);
            file.set(HungrySingleton.getInstance(), a); //把反射创建出来的对象赋值给单例对象
            System.out.println("第二次拿到单例模式创建的对象: " + HungrySingleton.getInstance());
        }
    }
}

不加情况:可以通过反射修改实例

反射创建出来的对象com.zy.singleton.HungrySingleton@1b6d3586
hello world
拿到单例模式创建的对象: com.zy.singleton.HungrySingleton@1540e19d
hello world
hello world
第二次拿到单例模式创建的对象: com.zy.singleton.HungrySingleton@1b6d3586

加的情况:会报出不能修改的异常信息

反射创建出来的对象com.zy.singleton.HungrySingleton@1b6d3586
Exception in thread "main" java.lang.IllegalAccessException: Can not set static final com.zy.singleton.HungrySingleton field com.zy.singleton.HungrySingleton.hungrySingleton to com.zy.singleton.HungrySingleton
hello world
	at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
拿到单例模式创建的对象: com.zy.singleton.HungrySingleton@1540e19d
hello world
	at sun.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
	at sun.reflect.UnsafeQualifiedStaticObjectFieldAccessorImpl.set(UnsafeQualifiedStaticObjectFieldAccessorImpl.java:77)
	at java.lang.reflect.Field.set(Field.java:764)
	at com.zy.Test2.main(Test2.java:30)

懒汉式单例

懒汉式单例的特点是:被外部类调用的时候内部类才会加载

public class LazySimpleSingleton {

    //静态代码块,公共内存区
    private static LazySimpleSingleton lazy = null;

    private LazySimpleSingleton(){}

    //存在线程安全问题
    public static LazySimpleSingleton getInstance(){
        if (lazy==null){
            lazy=new LazySimpleSingleton();
            System.out.println(11);
        }
        return lazy;
    }
}

思考:这个代码是否存在问题 ?
测试代码:

public class Test3 {
    public static void main(String[] args) {

        new Thread(() -> {
            LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
            System.out.println(instance);
        }, "A").start();

        new Thread(() -> {
            LazySimpleSingleton instance = LazySimpleSingleton.getInstance();
            System.out.println(instance);

        }, "B").start();

    }
}

在debug模式下采用Thread模式
在这里插入图片描述
开始 debug 之后,会看到 debug 控制台可以自由切换 Thread 的运行状态:
在这里插入图片描述通过不断切换线程,并观测其内存状态,我们发现在线程环境下LazySimpleSingleton被实例化了两次。有时,我们得到的运行结果可能是相同的两个对象,实际上是被后面执行的线程覆盖了,我们看到了一个假象,线程安全隐患依旧存在。那么,我们如何来优化代码,使得懒汉式单例在线程环境下安全呢?

给getInstance()加上synchronized关键字,让方法变成同步方法:

public class LazySimpleSingleton2 {

    private static LazySimpleSingleton2 lazy = null;

    private  LazySimpleSingleton2(){}

    public static synchronized LazySimpleSingleton2 getInstance(){
        if (lazy==null){
            lazy=new LazySimpleSingleton2();
            System.out.println(11);
        }
        return lazy;
    }
}

这时候,我们再来调试。当我们将其中一个线程执行并调用getInstance()方法时,另一 个线程在调用getInstance()方法,线程的状态由 RUNNING 变成了 MONITOR,出现阻 塞。直到第一个线程执行完,第二个线程才恢复RUNNING 状态继续调用 getInstance() 方法。

在这里插入图片描述
完美的展现了 synchronized 监视锁的运行状态,线程安全的问题便解决了。但是,用 synchronized加锁,在线程数量比较多情况下,如果 CPU 分配压力上升,会导致大批 量线程出现阻塞,从而导致程序运行性能大幅下降。那么,有没有一种更好的方式,既兼顾线程安全又提升程序性能呢?答案是肯定的。我们来看双重检查锁的单例模式:

public class LazySimpleSingleton3 {

    //为什么volatile
    //new 的过程1.分配内存空间 2.初始化对象 3.设置lazy指向公共分配的内存地址
    //jvm 指令重排
    //线程A,B,A执行1,3B进入,判断lazy不为空,返回了lazy,此时lazy还未实例化好
    private volatile static LazySimpleSingleton3 lazy = null;

    private LazySimpleSingleton3(){}

    private static LazySimpleSingleton3 getInstance(){
        if (lazy==null){
            synchronized (LazySimpleSingleton.class){
                //为什么
                //线程A,线程B 都通过第一个空判断
                //线程A抢占锁成功,实例完成
                //线程B进入,如果不判断就再次实例
                if (lazy==null){
                    lazy=new LazySimpleSingleton3();
                }
            }
        }
        return lazy;
    }
}

当第一个线程调用 getInstance()方法时,第二个线程也可以调用 getInstance()。当第一 个线程执行到synchronized 时会上锁,第二个线程就会变成 MONITOR 状态,出现阻 塞。此时,阻塞并不是基于整个LazySimpleSingleton 类的阻塞,而是在 getInstance()方法内部阻塞,只要逻辑不是太复杂,对于调用者而言感知不到。

但是,用到 synchronized 关键字,总归是要上锁,对程序性能还是存在一定影响的。难道就真的没有更好的方案吗?当然是有的。我们可以从类初始化角度来考虑,看下面的 代码,采用静态内部类的方式:

public class LazyInnerClassSingleton {
    //默认使用 LazyInnerClassGeneral 的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的
    private LazyInnerClassSingleton() {
    }

    //每一个关键字都不是多余的
    // static 是为了使单例的空间共享
    // 保证这个方法不会被重写,重载
    public static LazyInnerClassSingleton getInstance() {
        System.out.println(11);
        return LazyHolder.LAZY;
    }

    //默认不加载
    private static class LazyHolder {
       private static final LazyInnerClassSingleton LAZY = new LazyInnerClassSingleton();
    }
}

这种形式兼顾饿汉式的内存浪费,也兼顾 synchronized 性能问题,但是这个是安全的么?

public class Test4 {
    public static void main(String[] args) throws Exception {
        LazyInnerClassSingleton.getInstance();

        Class<LazyInnerClassSingleton> clazz = LazyInnerClassSingleton.class;
        Constructor c = clazz.getDeclaredConstructor();
        c.setAccessible(true);

        Object o1 = c.newInstance();
        Object o2 = c.newInstance();
        System.out.println(o1);
        System.out.println(o2);

    }
}

发现实例化两个对象

11
com.zy.singleton.LazyInnerClassSingleton@1b6d3586
com.zy.singleton.LazyInnerClassSingleton@4554617c

现在,我们在其构造方法中做一些限制,一旦出现多 次重复创建,则直接抛出异常。来看优化后的代码:

public class LazyInnerClassSingleton2 {
    //默认使用 LazyInnerClassGeneral 的时候,会先初始化内部类
    //如果没使用的话,内部类是不加载的
    private LazyInnerClassSingleton2() {
        if (LazyHolder.LAZY!=null){
            throw new RuntimeException("不允许创建多个实例");
        }
    }

    //每一个关键字都不是多余的
    // static 是为了使单例的空间共享
    // 保证这个方法不会被重写,重载
    public static LazyInnerClassSingleton2 getInstance() {
        System.out.println(11);
        return LazyHolder.LAZY;
    }
    //默认不加载
    private static class LazyHolder {
       private static final LazyInnerClassSingleton2 LAZY = new LazyInnerClassSingleton2();
    }
}

再运行测试代码,会得到以下结果:

Exception in thread "main" java.lang.reflect.InvocationTargetException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at com.zy.Test5.main(Test5.java:21)
Caused by: java.lang.RuntimeException: 不允许创建多个实例
	at com.zy.singleton.LazyInnerClassSingleton2.<init>(LazyInnerClassSingleton2.java:13)
	... 5 more

序列化破坏单例

当我们将一个单例对象创建好,有时候需要将对象序列化然后写入到磁盘,下次使用时 再从磁盘中读取到对象,反序列化转化为内存对象。反序列化后的对象会重新分配内存, 即重新创建。那如果序列化的目标的对象为单例对象,就违背了单例模式的初衷,相当 于破坏了单例,来看一段代码:

public class SeriableSingleton implements Serializable {

    private static final SeriableSingleton singleton = new SeriableSingleton();

    //序列化就是说把内存中的状态通过转换成字节码的形式
    // 从而转换一个 IO 流,写入到其他地方(可以是磁盘、网络 IO)
    // 内存中状态给永久保存下来了
    //
    // 反序列化
    // 讲已经持久化的字节码内容,转换为 IO 流
    // 通过 IO 流的读取,进而将读取的内容转换为 Java 对象
    // 在转换过程中会重新创建对象 new

    private SeriableSingleton() {
    }

    public static SeriableSingleton getInstance() {
        return singleton;
    }
}

测试代码:

public class Test6 {
    public static void main(String[] args) {
        SeriableSingleton s1=null;
        SeriableSingleton s2 =SeriableSingleton.getInstance();

        FileOutputStream fos=null;
        try {
            fos=new FileOutputStream("SeriableSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(s2);
            oos.flush();
            oos.close();

            FileInputStream fis = new FileInputStream("SeriableSingleton.obj");
            ObjectInputStream ois = new ObjectInputStream(fis);
            s1 = (SeriableSingleton) ois.readObject();
            ois.close();

            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s1==s2);


        } catch (Exception e) {
            e.printStackTrace();
        }


    }
}

运行结果:

com.zy.singleton.SeriableSingleton@6d03e736
com.zy.singleton.SeriableSingleton@14ae5a5
false

运行结果中,可以看出,反序列化后的对象和手动创建的对象是不一致的,实例化了两 次,违背了单例的设计初衷。那么,我们如何保证序列化的情况下也能够实现单例?其 实很简单,只需要增加 readResolve()方法即可。来看优化代码:

public class SeriableSingleton implements Serializable {

    private static final SeriableSingleton singleton = new SeriableSingleton();

    //序列化就是说把内存中的状态通过转换成字节码的形式
    // 从而转换一个 IO 流,写入到其他地方(可以是磁盘、网络 IO)
    // 内存中状态给永久保存下来了
    //
    // 反序列化
    // 讲已经持久化的字节码内容,转换为 IO 流
    // 通过 IO 流的读取,进而将读取的内容转换为 Java 对象
    // 在转换过程中会重新创建对象 new

    private SeriableSingleton() {
    }

    public static SeriableSingleton getInstance() {
        return singleton;
    }

    private Object readResolve() {
        return singleton;
    }
}

运行结果:

com.zy.singleton.SeriableSingleton@14ae5a5
com.zy.singleton.SeriableSingleton@14ae5a5
true

这是为什么?跟代码看看吧
进 入 ObjectInputStream 类的 readObject()方法,代码如下:
在这里插入图片描述
进 入readObject0()方法,代码如下(删除部分代码):

private Object readObject0(boolean unshared) throws IOException {
       。。。。。。。。
        try {
            switch (tc) {
                case TC_NULL:
                    return readNull();

                case TC_REFERENCE:
                    return readHandle(unshared);

                case TC_CLASS:
                    return readClass(unshared);

                case TC_CLASSDESC:
                case TC_PROXYCLASSDESC:
                    return readClassDesc(unshared);

                case TC_STRING:
                case TC_LONGSTRING:
                    return checkResolve(readString(unshared));

                case TC_ARRAY:
                    return checkResolve(readArray(unshared));

                case TC_ENUM:
                    return checkResolve(readEnum(unshared));

                case TC_OBJECT:
                    return checkResolve(readOrdinaryObject(unshared));

                case TC_EXCEPTION:
                    IOException ex = readFatalException();
                    throw new WriteAbortedException("writing aborted", ex);

                case TC_BLOCKDATA:
                case TC_BLOCKDATALONG:
                    if (oldMode) {
                        bin.setBlockDataMode(true);
                        bin.peek();             // force header read
                        throw new OptionalDataException(
                            bin.currentBlockRemaining());
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected block data");
                    }

                case TC_ENDBLOCKDATA:
                    if (oldMode) {
                        throw new OptionalDataException(true);
                    } else {
                        throw new StreamCorruptedException(
                            "unexpected end of block data");
                    }

                default:
                    throw new StreamCorruptedException(
                        String.format("invalid type code: %02X", tc));
            }
        } finally {
            depth--;
            bin.setBlockDataMode(oldMode);
        }
    }

主要代码:

case TC_OBJECT:
           return checkResolve(readOrdinaryObject(unshared));

进入readOrdinaryObject:

private Object readOrdinaryObject(boolean unshared)
        throws IOException
    {
        if (bin.readByte() != TC_OBJECT) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        desc.checkDeserialize();

        Class<?> cl = desc.forClass();
        if (cl == String.class || cl == Class.class
                || cl == ObjectStreamClass.class) {
            throw new InvalidClassException("invalid class descriptor");
        }

        Object obj;
        try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }

        passHandle = handles.assign(unshared ? unsharedMarker : obj);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(passHandle, resolveEx);
        }

        if (desc.isExternalizable()) {
            readExternalData((Externalizable) obj, desc);
        } else {
            readSerialData(obj, desc);
        }

        handles.finish(passHandle);

        if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

        return obj;
    }

主要代码:

........
 try {
            obj = desc.isInstantiable() ? desc.newInstance() : null;
        } catch (Exception ex) {
            throw (IOException) new InvalidClassException(
                desc.forClass().getName(),
                "unable to create instance").initCause(ex);
        }
        ..........
   }
........
if (obj != null &&
            handles.lookupException(passHandle) == null &&
            desc.hasReadResolveMethod())
        {
            Object rep = desc.invokeReadResolve(obj);
            if (unshared && rep.getClass().isArray()) {
                rep = cloneArray(rep);
            }
            if (rep != obj) {
                // Filter the replacement object
                if (rep != null) {
                    if (rep.getClass().isArray()) {
                        filterCheck(rep.getClass(), Array.getLength(rep));
                    } else {
                        filterCheck(rep.getClass(), -1);
                    }
                }
                handles.setObject(passHandle, obj = rep);
            }
        }

obj = desc.isInstantiable() ? desc.newInstance() : null;desc.isInstantiable()为true的时候会实例化一次,obj != null &&handles.lookupException(passHandle) == null & desc.hasReadResolveMethod()desc.hasReadResolveMethod()为true的时候会进行实例化赋值,进入两个方法看看。

isInstantiable()里面的代码 如下:

boolean isInstantiable() {
        requireInitialized();
        return (cons != null);
    }

代码非常简单,就是判断一下构造方法是否为空,构造方法不为空就返回 true。意味着只要有无参构造方法就会实例化。

再看看hasReadResolveMethod()方法:

boolean hasReadResolveMethod() {
        requireInitialized();
        return (readResolveMethod != null);
    }

逻辑非常简单,就是判断 readResolveMethod 是否为空,不为空就返回 true。那么 readResolveMethod 是在哪里赋值的呢?

readResolveMethod = getInheritableMethod(
                        cl, "readResolve", null, Object.class);

其实就是通过反射找到一个无参的 readResolve()方法,并且保存下来。

现在 再 回 到 ObjectInputStream 的 readOrdinaryObject() 方 法 继 续 往 下 看 , 如 果 readResolve()存在则调用 invokeReadResolve()方法,来看代码:

 Object invokeReadResolve(Object obj)
        throws IOException, UnsupportedOperationException
    {
        requireInitialized();
        if (readResolveMethod != null) {
            try {
                return readResolveMethod.invoke(obj, (Object[]) null);
            } catch (InvocationTargetException ex) {
                Throwable th = ex.getTargetException();
                if (th instanceof ObjectStreamException) {
                    throw (ObjectStreamException) th;
                } else {
                    throwMiscException(th);
                    throw new InternalError(th);  // never reached
                }
            } catch (IllegalAccessException ex) {
                // should not occur, as access checks have been suppressed
                throw new InternalError(ex);
            }
        } else {
            throw new UnsupportedOperationException();
        }
    }

我们可以看到在 invokeReadResolve()方法中用反射调用了 readResolveMethod 方法。到此基本说通原因,但是我们看到实际上实例化了两 次,只不过新创建的对象没有被返回而已。那如果,创建对象的动作发生频率增大,就意味着内存分配开销也就随之增大,难道真的就没办法从根本上解决问题吗?下面我们 来注册式单例也许能帮助到你。

注册式单例

注册式单例又称为登记式单例,就是将每一个实例都登记到某一个地方,使用唯一的标 识获取实例。注册式单例有两种写法:一种为容器缓存,一种为枚举登记。先来看枚举 式单例的写法,来看代码,创建 EnumSingleton 类:

public enum  EnumSingleton {
    INSTANCE;
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static EnumSingleton getInstance(){
        return INSTANCE;
    }
}

看看测试代码:

public class Test8 {
    public static void main(String[] args) throws Exception {
        EnumSingleton s1 = null;
        EnumSingleton s2 = EnumSingleton.getInstance();
        s2.setData(new Object());

        FileOutputStream fos = new FileOutputStream("EnumSingleton.obj");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(s2);
        oos.flush();
        oos.close();

        FileInputStream fis = new FileInputStream("EnumSingleton.obj");
        ObjectInputStream ois = new ObjectInputStream(fis);
        s1 = (EnumSingleton) ois.readObject();
        ois.close();

        System.out.println(s1.getData());
        System.out.println(s2.getData());
        System.out.println(s1.getData()==s2.getData());
    }
}

输出:

java.lang.Object@568db2f2
java.lang.Object@568db2f2
true

没有做任何处理,我们发现运行结果和我们预期的一样。那么枚举式单例如此神奇,它的神秘之处在哪里体现呢?下面我们通过分析源码来揭开它的神秘面纱。jad反编译根据。

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingleton.java

package com.zy.singleton;


public final class EnumSingleton extends Enum
{

    public static EnumSingleton[] values()
    {
        return (EnumSingleton[])$VALUES.clone();
    }

    public static EnumSingleton valueOf(String name)
    {
        return (EnumSingleton)Enum.valueOf(com/zy/singleton/EnumSingleton, name);
    }

    private EnumSingleton(String s, int i)
    {
        super(s, i);
    }

    public Object getData()
    {
        return data;
    }

    public void setData(Object data)
    {
        this.data = data;
    }

    public static EnumSingleton getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingleton INSTANCE;
    private Object data;
    private static final EnumSingleton $VALUES[];

    static 
    {
        INSTANCE = new EnumSingleton("INSTANCE", 0);
        $VALUES = (new EnumSingleton[] {
            INSTANCE
        });
    }
}

原来,枚举式单例在静态代码块中就给 INSTANCE 进行了赋值,是饿汉式单例的实现。但是为什么序列化也没有破坏?走进源码看看。。。

private Enum<?> readEnum(boolean unshared) throws IOException {
        if (bin.readByte() != TC_ENUM) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        if (!desc.isEnum()) {
            throw new InvalidClassException("non-enum class: " + desc);
        }

        int enumHandle = handles.assign(unshared ? unsharedMarker : null);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(enumHandle, resolveEx);
        }

        String name = readString(false);
        Enum<?> result = null;
        Class<?> cl = desc.forClass();
        if (cl != null) {
            try {
                @SuppressWarnings("unchecked")
                Enum<?> en = Enum.valueOf((Class)cl, name);
                result = en;
            } catch (IllegalArgumentException ex) {
                throw (IOException) new InvalidObjectException(
                    "enum constant " + name + " does not exist in " +
                    cl).initCause(ex);
            }
            if (!unshared) {
                handles.setObject(enumHandle, result);
            }
        }

        handles.finish(enumHandle);
        passHandle = enumHandle;
        return result;
    }

会发现Enum<?> en = Enum.valueOf((Class)cl, name);枚举类型其实通过类名和 Class 对象类找到一个唯一的枚举对像。那么反射能不能破坏呢?

public class Test9 {
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = EnumSingleton.class;
        Constructor c = clazz.getDeclaredConstructor();
        c.setAccessible(true);

        c.newInstance();
    }
}

运行结果:

Exception in thread "main" java.lang.NoSuchMethodException: com.zy.singleton.EnumSingleton.<init>()
	at java.lang.Class.getConstructor0(Class.java:3082)
	at java.lang.Class.getDeclaredConstructor(Class.java:2178)
	at com.zy.Test9.main(Test9.java:16)

报的是 java.lang.NoSuchMethodException 异常,意思是没找到无参的构造方法。这 时候,我们打开 java.lang.Enum 的源码代码,查看它的构造方法,只有一个 protected的构造方法,代码如下:

 protected Enum(String name, int ordinal) {
        this.name = name;
        this.ordinal = ordinal;
    }

到此枚举类型测试完成,强啊。。。。。

ThreadLocal 线程单例

ThreadLocal 不能保证其 创建的对象是全局唯一,但是能保证在单个线程中是唯一的,天生的线程安全。下面我 们来看代码:

public class ThreadLocalSingleton {
    private static final ThreadLocal<ThreadLocalSingleton> threadLocalInstancer
            = new ThreadLocal<ThreadLocalSingleton>() {
        @Override
        protected ThreadLocalSingleton initialValue() {
            return new ThreadLocalSingleton();
        }
    };

    private ThreadLocalSingleton(){}

    private static ThreadLocalSingleton getInstance(){
        return threadLocalInstancer.get();
    }

}

测试代码:

public class Test11 {
    public static void main(String[] args) {
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());
        System.out.println(ThreadLocalSingleton.getInstance());

        new Thread(() -> {
            System.out.println(ThreadLocalSingleton.getInstance());
        }).start();
        new Thread(() -> {
            System.out.println(ThreadLocalSingleton.getInstance());
        }).start();

        System.out.println("End");
    }
}

com.zy.singleton.ThreadLocalSingleton@1b6d3586
com.zy.singleton.ThreadLocalSingleton@1b6d3586
com.zy.singleton.ThreadLocalSingleton@1b6d3586
com.zy.singleton.ThreadLocalSingleton@1b6d3586
com.zy.singleton.ThreadLocalSingleton@1b6d3586
com.zy.singleton.ThreadLocalSingleton@4cc3632
End
com.zy.singleton.ThreadLocalSingleton@6dd2a02f

我们发现,在主线程 main 中无论调用多少次,获取到的实例都是同一个,都在两个子线 程中分别获取到了不同的实例。那么 ThreadLocal 是如果实现这样的效果的呢?我们知 道上面的单例模式为了达到线程安全的目的,给方法上锁,以时间换空间。ThreadLocal 将所有的对象全部放在 ThreadLocalMap 中,为每个线程都提供一个对象,实际上是以 空间换时间来实现线程间隔离的。

代码地址:https://github.com/zhengyao123/mini-gof.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值