【Java多线程编程实战案例】单例模式

单例模式

单例模式所要实现的目标非常简单:保持一个类有且仅有一个实例。注意,实现一个类有且仅有一个实例的前提是这个类是一个Java虚拟机实例中的一个Class Loader所加载的类。考虑到Java虚拟机的Class Loader机制:同一个类可以被多个Class Loader加载,这些Class Loader各自创建这个类的类实例。因此,如果有多个Class Loader加载同一个类,那么所谓的“单例”就无法满足——这些Class Loader各自的类实例都创建该类的唯一一个实例,实际上被创建的实例数就等于加载这个类的Class Loader的数量。

单线程版

出于性能考虑,不少单例模式的实现会采用延迟加载(Lazy Loading)的方式,即仅在需要用到相应实例的时候才创建实例。从单线程应用程序的角度理解,采用延迟加载实现的一个单例模式如下所示:

package com.company.ch3.case2;

public class SingleThreadedSingleton {
    // 保存该类的唯一实例
    private static SingleThreadedSingleton instance = null;

    //省略实例变量的声明
    /*
    私有构造器使其他类无法直接通过new创建该类的实例
     */
    private SingleThreadedSingleton() {
        //什么也不做
    }

    /**
     * 创建并返回该类的唯一实例
     * 即只有该方法被调用时该类的唯一实例才会被创建
     *
     * @return
     */
    public static SingleThreadedSingleton getInstance() {
        if (null == instance) { //操作①
            instance = new SingleThreadedSingleton(); //操作②
        }
        return instance;
    }

    public void someService() {
        //省略其他代码
    }
}

简单加锁实现

package com.company.ch3.case2;

public class SimpleMultithreadedSingleton {
    // 保存该类的唯一实例
    private static SimpleMultithreadedSingleton instance = null;

    /**
     * 私有构造器使其他类无法直接通过new创建该类的实例
     */
    private SimpleMultithreadedSingleton() {
        //什么也不做
    }

    /**
     * 创建并返回该类的唯一实例
     * 即只有该方法被调用时该类的唯一实例才会被创建
     *
     * @return
     */
    public static SimpleMultithreadedSingleton getInstance() {
        synchronized (SimpleMultithreadedSingleton.class) {
            if (null == instance) {
                instance = new SimpleMultithreadedSingleton();
            }
        }
        return instance;
    }

    public void someService() {
        //省略其他代码
    }
}

这种方法实现的单例模式意味着getInstance()的任何一个执行线程都需要申请锁。

基于双重检查锁定的单例模式

package com.company.ch3.case2;

public class DCLSingleton {
    /**
     * 保存该类的唯一实例,使用volatile关键字修饰instance
     */
    private static volatile DCLSingleton instance;

    /**
     * 私有构造器使其他类无法直接通过new创建该类的实例
     */
    private DCLSingleton() {
        //什么也不做
    }

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

    private void someService() {
        //省略其他代码
    }
}

将instance变量采用volatile修饰实际上是利用了volatile关键字的以下两个作用:

  • 保障可见性。一个线程通过执行操作修改了instance变量值,其他线程可以读取到相应的值。
  • 保障有序性。由于volatile能够禁止volatile变量写操作与该操作之前的任何读、写操作进行重排序。因此volatile修饰instance相当于禁止JIT编译器以及处理器将对对象进行初始化的写操作重排序到将对象引用写入共享变量的写操作,这保障了一个线程读取到instance变量所引用的实例时该实例已经初始化完毕。

基于静态内部类的单例模式实现

package com.company.ch3.case2;

import util.Debug;

public class StaticHolderSingleton {
    // 私有构造器
    private StaticHolderSingleton() {
        Debug.info("StaticHolderSingleton inited.");
    }

    private static class InstanceHolder {
        //保存外部类的唯一实例
        final static StaticHolderSingleton INSTANCE = new StaticHolderSingleton();
    }

    public static StaticHolderSingleton getInstance() {
        Debug.info("getInstance invoked");
        return InstanceHolder.INSTANCE;
    }

    private void someService() {
        Debug.info("SomeService invoked.");
        //省略其他代码
    }

    public static void main(String[] args) {
        StaticHolderSingleton.getInstance().someService();
    }
}

类的静态变量被初次访问会触发Java虚拟机对该类进行初始化,即该类的静态变量的值会变为其初始值而不是默认值。因此,静态方法getInstance()被调用的时候Java虚拟机会初始化这个方法所访问的内部静态类InstanceHolder。这使得InstanceHolder的静态内部类INSTANCE被初始化,从而使StaticHolderSingleton类的唯一实例得以创建。由于类的静态变量只会创建一次,因此StaticHolderSingleton只会被创建一次。

基于枚举类型的单例模式

package com.company.ch3.case2;

import util.Debug;

public class EnumBasedSingleton {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                Debug.info(Singleton.class.getName());
                Singleton.INSTANCE.someService();
            }
        };
        t.start();
    }

    public static enum Singleton {
        INSTANCE;

        // 私有构造器
        Singleton() {
            Debug.info("Singleton inited.");
        }

        public void someService() {
            Debug.info("someService invoked.");
            // 省略其他代码
        }
    }
}

这里的枚举类型Singleton相当于一个单例类,其字段INSTANCE值相当于该类的唯一实例。这个实例是在Singleton.INSTANCE初次被引用的时候才被初始化的。仅访问Singleton本身并不会导致Singleton的唯一实例被初始化。

参考资料

《Java多线程编程实战指南》

发布了226 篇原创文章 · 获赞 129 · 访问量 24万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 黑客帝国 设计师: 上身试试

分享到微信朋友圈

×

扫一扫,手机浏览