设计模式-单例模式

232 阅读1分钟

单例模式的优点

  • 单例模式的使用可以减少内存的开支、减少系统性能的消耗、避免资源的多重占用,例如一个写文件动作,由于只有一个实例存在于内存中,避免了对同一个资源文件同时写操作

单例模式的使用场景

  • 要求生成唯一序列号的环境
  • 在项目中需要一个共享访问点或共享数据,例如一个WEB页面的计数器,可以每次不用将刷新数据记录到数据库中,使用单例模式保持计数器的值,并且保证是线程安全的
  • 创建一个对象需要消耗的资源过多
  • 需要定义大量的静态常量和静态方法(如工具类)的环境,可以采用单例模式(当然,也可以直接声明为static的方式)

单例模式的实现方式

饿汉式

/**
 * @author cuckooYang
 * @create 2020-08-03 14:36
 * @description 饿汉式案例实现(线程安全)
 **/

public class IdGenerator {
    private Long id = new Random().nextLong();
    private static IdGenerator instance = new IdGenerator();
    private IdGenerator() {
    }
    public  static IdGenerator getInstance(){
        return instance;
    }
    public Long getId(){
        return id;
    }
}
public class IdGeneratorMain1 {
    public static void main(String[] args) {
        for(int i =0; i< 30; i++){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    Long id = IdGenerator.getInstance().getId();
                    System.out.println(Thread.currentThread().getName()+" "+id+"");
                }
            });
            thread.start();
        }
    }
}

双重检测

/**
 * @author cuckooYang
 * @create 2020-08-03 15:12
 * @description 双重检测
 **/

public class IdGenerator {
    private Long id = new Random().nextLong();
    private static IdGenerator instance;
    private IdGenerator(){}

    public static IdGenerator getInstance(){
        if(instance == null){
            synchronized (IdGenerator.class){
                if(instance == null){
                    instance = new IdGenerator();
                }
            }
        }
        return instance;
    }
    public Long getId(){
        return  id;
    }
}

/**
 * @author cuckooYang
 * @create 2020-08-03 15:24
 * @description
 **/

public class IdGeneratorMain {

    public static void main(String[] args) {
        for(int i=0; i<10; i++){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    IdGenerator idGenerator = IdGenerator.getInstance();
                    System.out.println(Thread.currentThread().getName()+" "+idGenerator.getId());
                }
            });
            thread.start();
        }

    }
}

静态内部类

/**
 * @author cuckooYang
 * @create 2020-08-03 15:41
 * @description 静态内部类
 **/

public class IdGenerator {
    private Long id = new Random().nextLong();
    private IdGenerator(){}
    private static class SingletonHolder{
        private static final IdGenerator instance = new IdGenerator();

    }
    public static IdGenerator getInstance(){
        return SingletonHolder.instance;
    }
    public Long getId(){
        return id;
    }
}

SingletonHolder是一个静态内部类,当外部类IdGenerator类被加载时,并不会创建SingletonHolder实例对象,只有当调用getInstance()方法时,SingletonHolder才会被加载,这个时候才会创建instance,instance的唯一性和创建线程的安全性都是由JVM来保证的,这种方法即实现了线程安全,又能做到延迟加载

枚举方式

这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。 它更简洁,自动支持序列化机制,绝对防止多次实例化 (如果单例类实现了Serializable接口,默认情况下每次反序列化总会创建一个新的实例对象,关于单例与序列化的问题可以查看这一篇文章《单例与序列化的那些事儿》

/**
 * 通过枚举方式
 */
public enum IdGenerator {
    INSTANCE;
    private Long id = new Random().nextLong();
    public Long getId(){
        return id;
    }
}

/**
 * @author cuckooYang
 * @create 2020-08-03 14:49
 * @description
 **/

public class IdGeneratorMain1 {
    public static void main(String[] args) {
        for(int i =0; i< 30; i++){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    IdGenerator instance = IdGenerator.INSTANCE;
                    System.out.println(Thread.currentThread().getName()+" "+instance.getId());

                }
            });
            thread.start();
        }
    }
}

单例存在的问题

  • 单例对于OOP的特性支持不好
    • IdGenerator的使用方式违背了基于接口而非实现的设计原则,也违背了广义上的理解OOP抽象特性。单例对于继承多态特性的支持不好。
  • 单例会隐藏类之间的依赖关系
    • 通过构造函数,参数传递等方式声明的类之间的依赖关系,我们可以通过函数的定义来查看,但是,单例类不需要创建,如果代码比较复杂,这种调用关系就会比较隐蔽
  • 单例对于代码的扩展性不好
    • 如果哪天我们需要创建多个实例,对代码的改动量就比较大,比如数据库连接池
  • 单例对于代码的可测试性不友好
  • 单例不支持有参数的构造参数

线程间创建唯一对象

/**
 * @author cuckooYang
 * @create 2020-08-04 14:21
 * @description 线程间创建唯一对象
 **/

public class IdGenerator {
    private Long id = new Random().nextLong();
    private static ThreadLocal<IdGenerator> threadLocal = new ThreadLocal<>();
    private IdGenerator(){}
    public static IdGenerator getInstance(){
        if(threadLocal.get() == null){
            threadLocal.set(new IdGenerator());
        }
        System.out.println(Thread.currentThread().getId()+" - "+threadLocal.get().hashCode());
        return threadLocal.get();
    }
    public long getId(){
        return id;
    }
}
/**
 * @author cuckooYang
 * @create 2020-08-04 14:27
 * @description
 **/

public class ThreadMain {
    public static void main(String[] args) {
        for(int i=0; i<50; i++){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    Long id1 = IdGenerator.getInstance().getId();
                    Long id2 = IdGenerator.getInstance().getId();
                    System.out.println(Thread.currentThread().getId()+"  id1:"+id1+"  id2:"+id2);
                }
            });
            thread.start();
        }
    }
}

多例模式

/**
 * @author cuckooYang
 * @create 2020-08-04 14:56
 * @description 多例模式
 **/

public class Emperor {
    private static int maxNumOfEmperror =2;
    private static List<String> nameList = new ArrayList<>();
    private static List<Emperor> emperors = new ArrayList<>();
    private static int count = 0;
    static{
        for(int i=0;i< maxNumOfEmperror;i++){
            emperors.add(new Emperor("皇帝"+i+"号"));
        }
    }
    private Emperor(){}
    private Emperor(String name){
        nameList.add(name);
    }
    public static Emperor getInstance(){
        Random random = new Random();
        count = random.nextInt(maxNumOfEmperror);
        System.out.println(emperors.get(count).hashCode());
        return emperors.get(count);
    }
    public void say(){
        System.out.println(nameList.get(count));
    }

}
/**
 * @author cuckooYang
 * @create 2020-08-04 15:03
 * @description
 **/

public class ThreadMain {
    public static void main(String[] args) {
        for(int i=0; i<50; i++){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    Emperor.getInstance().say();
                }
            });
            thread.start();
        }
    }
}