五种基本单例模式

目录标题

  • 模式简介
  • 饿汉式
  • 懒汉式
  • 懒汉式(双重检验锁)
  • 静态内部类
  • 枚举类型

模式简介

  • 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

  • 单例模式是一种常用的软件设计模式,其定义是单例对象的类只能允许一个实例存在。这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

  • 许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

注意:

1、单例类只能在一个jvm实例中有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。

接下来我们介绍一下我们常见到的几种单例模式:

饿汉式

正如其名"饿汉" ,类加载的时候就实例化,并且创建单例对象。优点: 先天性线程安全 当类初始化的时候 该对象就会被创建
缺点 : 如果项目中使用过多的饿汉式就会发生问题,项目启动会很慢,存放在方法区中占用内存较大

public class demo1 {
    /*
       饿汉式 :  优点 先天性线程安全 当类初始化的时候  该对象就会被创建
                缺点 : 如果项目中使用过多的饿汉式就会发生问题,项目启动会很慢,存放在方法区中占用内存较大
     */
    private  static demo1  singleTon = new demo1();
    private demo1(){

    }
    public static demo1 getInstance(){
        return singleTon;
    }
}

懒汉式

只有在需要的时候才会加载 但是在多线程的情况下 会出现线程安全的问题. 实例对象可能被初始化多次

public class demo2 {
    private static demo2  singleTon;
    //懒汉式的单例模式
    private  demo2(){

    }
    public static demo2 getInstance(){
        if(singleTon==null){     
            singleTon = new demo2();
        }
        return singleTon;
    }
}

对于饿汉式它会存在,在多线程的情况下,线程不安全的问题,实例化的对象(单例对象)可能被实例化多次,这里我们做一个测试

public class test {
    public static void main(String[] args) {
         for(int i=0;i<300;i++){
             new Thread(new Runnable() {
                 public void run() {
                     demo2 singleTon = demo2.getInstance();
                     System.out.println(Thread.currentThread().getName()+","+singleTon);
                 }
             }).start();
         }
    }
}
public class demo2 {
    private static demo2  singleTon;
    //懒汉式的单例模式
    private  demo2(){

    }
    public  static demo2 getInstance(){
        if(singleTon==null){
            try{
                //这里线程睡眠更容易看到效果
                Thread.sleep(2000);
            }catch(Exception e){

            }
            singleTon = new demo2();
        }
         return singleTon;
    }
}

结果:

Thread-14,单例模式.service.懒汉式.demo2@32b9f1f0
Thread-10,单例模式.service.懒汉式.demo2@7070e0ed
Thread-4,单例模式.service.懒汉式.demo2@1ea98862
Thread-7,单例模式.service.懒汉式.demo2@294eb59e
Thread-3,单例模式.service.懒汉式.demo2@26dc9ee3
Thread-1,单例模式.service.懒汉式.demo2@5e0d2717
Thread-27,单例模式.service.懒汉式.demo2@b20b63
Thread-6,单例模式.service.懒汉式.demo2@4b0d0bfe
Thread-16,单例模式.service.懒汉式.demo2@3e81761
Thread-0,单例模式.service.懒汉式.demo2@7b32301d
Thread-8,单例模式.service.懒汉式.demo2@7b32301d
Thread-9,单例模式.service.懒汉式.demo2@7b32301d
Thread-11,单例模式.service.懒汉式.demo2@21ed9a86

这里只截取了运行结果的一部分,剩余部分因为长度问题,本文没有全部截取,读者可以截取上述代码,自行运行,从结果我们可以看到singleTon对象,在多个线程中实例化了多次,这就违背了单例模式的设计原则.那在多线程情况下如何解决懒汉式存在线程安全的问题呢? 别急,接着往下看。

懒汉式(双重检验锁)

对于多线程的情况下,我们对于懒汉式问题可以给它加锁,来保证线程同步

 public synchronized static demo2 getInstance(){
        if(singleTon==null){
            try{
                Thread.sleep(2000);
            }catch(Exception e){

            }
            singleTon = new demo2();
        }
         return singleTon;
    }

这样加锁可以保证懒汉式在多线程情况下的线程安全问题,但是有一个致命的缺点就是加锁在高并发的情况下,会影响程序的执行效率.那我们如何保证线程安全的情况下,加快程序运行的效率呢 ,这里我们介绍一种双重检验锁。
什么是双重检验锁呢,双重检验锁是为了解决懒汉式 对于读和写都加锁的问题,在单例模式中,我们只有第一次访问该对象的时候需要加锁,对象才需要实例化,之后再次访问我们就没有必要再去加锁,只是读的操作。我们可以在对象创建的时候进行双重判断,来保证线程安全,并且提高运行效率.

public class demo3{
    private static demo3 singleTon;
    private demo3() throws Exception {
        if(singleTon!=null){
            throw new Exception("对象已经初始化");
        }
        System.out.println("初始化双重检验锁");
    }
    /*
      只有在需要的时候才会加载 但是在多线程的情况下 会出现线程安全的问题
       实例对象可能被初始化多次
     */
    public synchronized static demo3 getInstance() throws Exception {
        if(singleTon==null){
            synchronized(demo3.class){
                if(singleTon == null){
                    //当前线程已经获得锁对象 判断当前对象是否初始过
                    singleTon = new demo3();
                }
            }
        }
        return singleTon;
    }
}

静态内部类

public class SingleTon{
  private SingleTon(){}
 
  private static class SingleTonHoler{
     private static SingleTon INSTANCE = new SingleTon();
 }
 
  public static SingleTon getInstance(){
    return SingleTonHoler.INSTANCE;
  }
}

静态内部类的优点是:外部类加载时并不需要立即加载内部类,内部类不被加载则不去初始化INSTANCE,故而不占内存。即当SingleTon第一次被加载时,并不需要去加载SingleTonHoler,只有当getInstance()方法第一次被调用时,才会去初始化INSTANCE,第一次调用getInstance()方法会导致虚拟机加载SingleTonHoler类,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。个人认为是懒汉式和饿汉式的升级版 ,这里推荐大家一篇博客,博主对于静态内部类的实现,讲的比较详细: 深入理解单例模式:静态内部类单例原理

枚举类型

public enum demo4 {
    //枚举能够先天性破解序列化和反射
     Instance;
     public void add(){
         System.out.println("add方法...");
     }
}

测试类

public class Test {
    public static void main(String[] args) {
        demo4 instance = demo4.Instance;
        instance.add();
        demo4.Instance.add(); 
        demo4 instance2 = demo4.Instance;
        System.out.println(instance == instance2);
    }
}
/*
add方法...
add方法...
true
*/

在枚举方式实现的单例模式中,在最终编译后的class文件上,是将enum转化为一个类,例如上述的例子里,这里我们使用jclasslib工具来查看demo4的字节码文件
五种基本单例模式_第1张图片这里我们可以看到在字节码文件中,demo4这个枚举类,最终是以class类的形式存在,继承了Enum类,其中的Instance最终是以实例的形式存在,这就是为什么可以使用

demo4.Instance.add();

来操作add方法
五种基本单例模式_第2张图片

你可能感兴趣的:(java,设计模式,java,设计模式,单例模式)