Java单例?kotlin单例?你真的会用单例么?反正我面试过的人会的没几个

Posted 王二蛋和他的狗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java单例?kotlin单例?你真的会用单例么?反正我面试过的人会的没几个相关的知识,希望对你有一定的参考价值。

前言

作为一家公司的android技术主管,面试是一件比较爽的事,一般来说我面到的都是水平不如我的(主要公司面试岗位不是很高,一般来说是中级或高级工程师),那么作为主管的我,对于人员的筛选上,设计模式比较问的多。然而真正能说的出设计模式的人少之又少。很难让我满意。尤其是及其常用的单例模式。

单例是什么?

是一种对象创建模式,可以确保项目中一个类只产生一个实例。

好处

对于频繁使用的对象可以减少创建对象所花费的时间,这对于重量级对象来说,简直是福音。由于new的减少,对系统内存使用频率也会降低,减少GC的压力,并缩短GC停顿时间,这也会减少Android项目的UI卡顿。

java的单例及其涉及的知识点

  1. 饿汉模式
public class TestSingleton {

    private static final TestSingleton testSingleton = new TestSingleton();

    private TestSingleton(){

    }

    public static TestSingleton getInstance(){
        return testSingleton;
    }

}

细节我就不多写了,大家都应该知道,构造函数为private,用getInstance来获取实例

  1. 懒汉模式
public class TestSingleton {

    private static TestSingleton testSingleton;

    private TestSingleton(){

    }

    public static TestSingleton getInstance(){
        if(testSingleton==null){
            testSingleton = new TestSingleton();
        }
        return testSingleton;
    }

}

比饿汉式的优点在于用时再加载,比较重量级的单例,就不适用与饿汉了。

  1. 线程安全的懒汉模式
public class TestSingleton {

    private static TestSingleton testSingleton;

    private TestSingleton(){

    }

    public static TestSingleton getInstance(){
        if(testSingleton==null){
            synchronized (TestSingleton.class){
                testSingleton = new TestSingleton();
            }
        }
        return testSingleton;
    }

}

可以看到的是比上面的单例多了一个对象锁,着可以保证在创建对象的时候,只有一个线程能够创建对象。

  1. 线程安全的懒汉模式-DCL双重检查锁机制
public class TestSingleton {

    private static volatile TestSingleton testSingleton;

    private TestSingleton(){

    }

    public static TestSingleton getInstance(){
        if(testSingleton==null){
            synchronized (TestSingleton.class){
                if(testSingleton==null){
                    testSingleton = new TestSingleton();
                }
            }
        }
        return testSingleton;
    }

}

双重检查,同步块加锁机制,保证你的单例能够在加锁后的代码里判断空,还有增加了一个volatile 关键字,保证你的线程在执行指令时候按顺序执行。这也是市面上见的最多的单例。

敲黑板!!知识点:原子操作、指令重排。

什么是原子操作?

简单来说,原子操作(atomic)就是不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。

m = 6; // 这是个原子操作

假如m原先的值为0,那么对于这个操作,要么执行成功m变成了6,要么是没执行m还是0,而不会出现诸如m=3这种中间态——即使是在并发的线程中。

而,声明并赋值就不是一个原子操作:

int n = 6; // 这不是一个原子操作

对于这个语句,至少有两个操作:

声明一个变量n
给n赋值为6
这样就会有一个中间状态:变量n已经被声明了但是还没有被赋值的状态。

在多线程中,由于线程执行顺序的不确定性,如果两个线程都使用m,就可能会导致不稳定的结果出现。

什么是指令重排?

简单来说,就是计算机为了提高执行效率,会做的一些优化,在不影响最终结果的情况下,可能会对一些语句的执行顺序进行调整。

int a ; // 语句1

a = 8 ; // 语句2

int b = 9 ; // 语句3

int c = a + b ; // 语句4

正常来说,对于顺序结构,执行的顺序是自上到下,也即1234。

但是,由于指令重排的原因,因为不影响最终的结果,所以,实际执行的顺序可能会变成3124或者1324。

由于语句3和4没有原子性的问题,语句3和语句4也可能会拆分成原子操作,再重排。

也就是说,对于非原子性的操作,在不影响最终结果的情况下,其拆分成的原子操作可能会被重新排列执行顺序。

主要在于testSingleton = new TestSingleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。

给 testSingleton 分配内存
调用 testSingleton 的构造函数来初始化成员变量,形成实例
将testSingleton 对象指向分配的内存空间(执行完这步 testSingleton 才是非 null 了)
但是在 JVM 的即时编译器中存在指令重排序的优化。也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 testSingleton 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。

推荐后两种来实现单例

  1. 静态内部类来实现单例

    public class TestSingleton {
    
     private TestSingleton(){
    
     }
    
     public static TestSingleton getInstance(){
         return TestSingletonInner.testSingleton;
     }
    
     private static class TestSingletonInner{
         static final TestSingleton testSingleton = new TestSingleton();
     }
    
    }

    static 保证数据独一份

final 初始化完成后不能被修改,线程安全。

敲黑板!!知识点:java在加载类的时候不会将其内部的静态内部类加载,只有在使用该内部类方法时才被调用。这明显是最好的单例,并不需要什么锁一类的机制。

利用了类中静态变量的唯一性

优点:

jvm本身机制保证线程安全。
synchronized 会导致性能问题。
TestSingletonInner 是私有的,除了通过TestSingleton 访问,没有其他访问的可能性。

  1. 枚举单例
public enum  TestSingleton {

    INSTANCE;

    public void toSave(){
    }

}

使用TestSingleton.INSTANCE.toSave();

创建枚举实例的过程是线程安全的,所以这种写法也没有同步的问题。如果你要自己添加一些线程安全的方法,记得控制线程安全哦。

优点:写法简单/线程安全

kotlin的单例

  1. 饿汉式实现
    object SingletonDemo
  2. 懒汉式
class Singleton private constructor() {
    companion object {
        var instance: Singleton? = null
            get() {
                if (field == null) {
                    field = Singleton()
                }
                return field
            }
        private set

    }
}
  1. 线程安全的懒汉式

class Singleton private constructor() {
    companion object {
        var instance: Singleton? = null
            @Synchronized
            get() {
                if (field == null) {
                    field = Singleton()
                }
                return field
            }
            private set
    }

}
  1. 双重校验锁式
class Singleton private constructor() {
    companion object {
        val instance: Singleton by lazy {
        Singleton() }
    }
}
  1. 静态内部类式
class Singleton private constructor() {
    companion object {
        val instance = SingletonHolder.holder
    }

    private object SingletonHolder {
        val holder= Singleton()
    }

}

我个人在用kotlin单例的话我还是喜欢直接用lazy,方便,嘻嘻嘻

Android源码中的单例

通常我们会用context.getSystemService(String name)来获取一些系统服务
如下:

ActivityManager  mActivityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);  

例如:LayoutInflater


package android.view;
public static LayoutInflater from(Context context) {
        LayoutInflater LayoutInflater =
                (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        if (LayoutInflater == null) {
            throw new AssertionError("LayoutInflater not found.");
        }
        return LayoutInflater;
    }

Context 部分源码


public abstract class Context {

    public abstract Object getSystemService(@ServiceName @NonNull String name);

}

通过分析activity的启动流程可以知道,Context的功能的具体实现是在ContextImpl.java


class ContextImpl extends Context {
...
    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }
}

然后继续SystemServiceRegistry.getSystemService(this, name):

final class SystemServiceRegistry {
    ...
//用来getSystemService的容器,里面存放的是ServiceFetcher<?>
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();
...
//静态代码块,第一次加载时执行,而且只会执行一次,保证了注册的服务的唯一性。
    static {
        registerService(Context.ACCESSIBILITY_SERVICE, AccessibilityManager.class,
                new CachedServiceFetcher<AccessibilityManager>() {
            @Override
            public AccessibilityManager createService(ContextImpl ctx) {
                return AccessibilityManager.getInstance(ctx);
            }});
        registerService(Context.DOWNLOAD_SERVICE, DownloadManager.class,
                new CachedServiceFetcher<DownloadManager>() {
            @Override
            public DownloadManager createService(ContextImpl ctx) {
                return new DownloadManager(ctx);
            }});
        ...
//还有很多服务注册
    }
    
  ...
//静态代码块中调用这个方法,把服务名和创建的服务对应放在容器中,实现单例。
      private static <T> void registerService(String serviceName, Class<T> serviceClass,
            ServiceFetcher<T> serviceFetcher) {
        SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
        SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
    }
  ...
  public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
  ...
}

里面还不是直接拿到服务,而是调用了fetcher.getService(ctx)来获取服务。看看

ServiceFetcher<?>:

static abstract interface ServiceFetcher<T> {
        T getService(ContextImpl ctx);
    }

这是个接口,看上面的静态代码块里面的方法发现注册服务的时候都是用的CachedServiceFetcher这个类:

static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
        private final int mCacheIndex;

        public CachedServiceFetcher() {
            mCacheIndex = sServiceCacheSize++;
        }

        @Override
        @SuppressWarnings("unchecked")
        public final T getService(ContextImpl ctx) {
//ctx.mServiceCache是获取一个数组:new Object[sServiceCacheSize];
//数组的长度就是构造方法中的那个变量,每注册一个服务,就会new一个对应的CachedServiceFetcher,然后数组长度就+1。第一次获取到这个数组肯定是个空数组
            final Object[] cache = ctx.mServiceCache;
            synchronized (cache) {
                // Fetch or create the service.
                Object service = cache[mCacheIndex];
//第一次获取这个服务的时候,数组是空的 ,所以service == null为TRUE。
                if (service == null) {
//调用注册时实现的createService方法,把生成的具体服务放在数组对应下标中,
//之后就直接从数组中获取了。实现了单例。
                    service = createService(ctx);
                    cache[mCacheIndex] = service;
                }
                return (T)service;
            }
        }
      //  在静态代码块中实现
        public abstract T createService(ContextImpl ctx);
    }

里面有个抽象方法,需要实例化的时候实现。在静态代码块中的方法都实现了这个createService(ContextImpl ctx)方法,并且返回了对应的服务。

以上是关于Java单例?kotlin单例?你真的会用单例么?反正我面试过的人会的没几个的主要内容,如果未能解决你的问题,请参考以下文章

你研究过单例么?这样写单例效率最高.

单例设计模式你真的会用吗?

聊透设计模式之单例模式

这 9 种单例模式你都会吗?

自己写的程序有必要用单例吗

请问java 单例类 与 静态类 有何不同?