实现单例设计模式的多种方式

1. 关于单例设计模式

Singleton:在Java中即指单例设计模式,它是软件开发中最常用的设计模式之一。
单:唯一
例:实例

单例设计模式,即某个类在整个系统中只能有一个实例对象可被获取和使用的代码模式。
例如:代表JVM运行环境的Runtime类

要点:

(1)某个类只能有一个实例:构造器私有化
(2)它必须能够创建该类实例:使用该类的静态变量来保存这个唯一的实例。
(3)它必须能返回该实例:通过get方法返回该实例或直接暴露给外部.

2. 单例设计模式的种类

2.1 饿汉式:直接创建对象,不存在线程安全问题

2.1.1 直接实例化饿汉式(简洁直观)

/*
 * 饿汉式:
 *  在类初始化时直接创建实例对象,不管是否需要这个对象都会创建
 * 单例创建要求:
 * (1)构造器私有化
 * (2)自行创建,并且用静态变量保存
 * (3)向外提供这个实例
 * (4)强调这是一个单例,我们可以用final修改
 */
public class Singleton1 {
    public static final Singleton1 INSTANCE = new Singleton1();
    private Singleton1(){

    }
    public static void main(String[] args) {
        Singleton1 s = Singleton1.INSTANCE;
        System.out.println(s);
    }
}

2.1.2 枚举式(最简洁)

package com.bigdata.juc.singleton;
/*
 * 枚举类型:表示该类型的对象是有限的几个
 * 我们可以限定为一个,就成了单例
 */
enum EnumSingleton{
    INSTANCE
}
public class Singleton2 {
    public static void main(String[] args) {
        EnumSingleton s = EnumSingleton.INSTANCE;
        System.out.println(s);
    }
}

2.1.3 静态代码块饿汉式(适合复杂实例化)

//静态代码块恶汉式
public class Singleton3 {
    public static final Singleton3 INSTANCE;
    public String info;
    private Singleton3(String info){
        this.info = info;
    }
    static{
        try {
            Properties pro = new Properties();

            pro.load(Singleton3.class.getClassLoader().getResourceAsStream("single.properties"));

            INSTANCE = new Singleton3(pro.getProperty("info"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    public static void main(String[] args) {
        Singleton3 s = Singleton3.INSTANCE;
        System.out.println(s.info);
    }
}

2.2 懒汉式:延迟创建对象

2.2.1 线程不安全(适用于单线程)

import java.util.concurrent.*;

/*
 * 懒汉式:
 *  延迟创建这个实例对象,可能会出现线程不安全的情况
 *
 * (1)构造器私有化
 * (2)用一个静态变量保存这个唯一的实例
 * (3)提供一个静态方法,获取这个实例对象
 */
public class Singleton4 {
    private static Singleton4 instance;
    private Singleton4(){

    }
    public static Singleton4 getInstance(){
        if(instance == null){

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            instance = new Singleton4();
        }
        return instance;
    }
    public static void main(String[] args) throws InterruptedException, ExecutionException {

        //检查这种创建方式的线程安全性
        Callable<Singleton4> c = new Callable<Singleton4>() {

            @Override
            public Singleton4 call() throws Exception {
                return Singleton4.getInstance();
            }
        };
        //使用newFixedThreadPool线程池,设置线程数为2
        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<Singleton4> f1 = es.submit(c);
        Future<Singleton4> f2 = es.submit(c);

        Singleton4 s1 = f1.get();
        Singleton4 s2 = f2.get();

        System.out.println(s1 == s2);//false 说明这种创建单例的方式,在多线程的方式下并不安全
        System.out.println(s1);
        System.out.println(s2);

        es.shutdown();

//        new Thread(()->{
//            System.out.println(Thread.currentThread().getName()+":"+Singleton4.getInstance());
//        },"A").start();
//        new Thread(()->{
//            System.out.println(Thread.currentThread().getName()+":"+Singleton4.getInstance());
//        },"B").start();

    }
}

2.2.2 线程安全(适用于多线程)

/*
 * 懒汉式:
 *  延迟创建这个实例对象,针对于线程不安全的问题,可以在创建的时候,使用lock或synchronize来解决
 *
 * (1)构造器私有化
 * (2)用一个静态变量保存这个唯一的实例
 * (3)提供一个静态方法,获取这个实例对象
 */
public class Singleton5 {
    private static Singleton5 instance;
    private Singleton5(){

    }
    //并不推荐使用同步方法的方式,来完成单例模式设计,因为它锁了整个方法,其他线程想要获取实例只能等待,显然效率比较低,推荐使用DCL(Double Check Lock 双端检锁机制)来完成单例设计
    //  public synchronized static Singleton5 getInstance(){
    public static Singleton5 getInstance(){
        if(instance == null){
            //使用同步代码块来解决单例创建过程中的线程不安全问题,也可以使用同步方法来实现
            synchronized (Singleton5.class) {
                if(instance == null){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    instance = new Singleton5();
                }
            }
        }
        return instance;
    }
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        //验证多线程环境下,这种创建线程的方式是否安全
        Callable<Singleton5> c = new Callable<Singleton5>() {

            @Override
            public Singleton5 call() throws Exception {
                return Singleton5.getInstance();
            }
        };

        ExecutorService es = Executors.newFixedThreadPool(2);
        Future<Singleton5> f1 = es.submit(c);
        Future<Singleton5> f2 = es.submit(c);

        Singleton5 s1 = f1.get();
        Singleton5 s2 = f2.get();

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

        es.shutdown();

    }
}

2.2.3 静态内部类形式(适用于多线程)

/*
 * 在内部类被加载和初始化时,才创建INSTANCE实例对象
 * 静态内部类不会自动随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的。
 * 因为是在内部类加载和初始化时,创建的,因此是线程安全的
 */
public class Singleton6 {
    private Singleton6(){

    }
    private static class Inner{
        private static final Singleton6 INSTANCE = new Singleton6();
    }

    public static Singleton6 getInstance(){
        return Inner.INSTANCE;
    }
}

3.总结:

  • 如果是饿汉式,枚举形式最简单
  • 如果是懒汉式,静态内部类形式最简单

猜你喜欢

转载自www.cnblogs.com/cosmos-wong/p/11914878.html