Java单例模式之总有你想不到的知识

Network
• 阅读 339

文章目录

Java单例模式

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

单例模式确保在一个应用程序中某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例 单例实例。

满足条件

单例模式只应在有真正的“单一实例”的需求时才可使用:

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

两种形式

Java中实现单例模式可以通过两种形式实现:

  • 懒汉模式 (类加载时不初始化)
  • 饿汉模式 (在类加载时就完成了初始化,所以类加载较慢,但获取对象的速度快)

设计要求

编写单例必须满足下面的条件:

  1. 构造方法变成私有
  2. 提供一个静态方法获取单实例对象

饿汉模式

饿汉模式基于classloader机制避免了多线程的同步问题(静态初始化将保证在任何线程能够访问到域之前初始化它),不过,instance在类装载时就实例化,这时候初始化instance显然没有达到懒加载(lazy loading)的效果

饿汉单例相对比较容易理解,一般表现为以下两种形式:

package com.shixun.design.singleton;

public class  Singleton1  { 
    private static Singleton1 instance = new Singleton1();

    // 私有构造方法,保证外界无法直接实例化。 
    private  Singleton1()  { 
    }
    // 通过公有的静态方法获取对象实例 
    public  static  Singleton1  getInstance()  { 
        return instance;
    }
}

也可以将静态对象初始化放在静态代码块中

package com.shixun.design.singleton;

public class  Singleton2  { 
    private static Singleton2 instance = null;
    // 对象初始化放在静态代码块中
    static { 
        instance = new Singleton2();
    }
    // 私有构造方法,保证外界无法直接实例化。
    private  Singleton2()  { 
    }

    // 通过公有的静态方法获取对象实例
    public  static  Singleton2  getInstance()  { 
        return instance;
    }
}

懒汉方式

实现单例模式能够提高类加载性能,但是和饿汉模式借助与JVM的类加载内部同步机制实现了线程安全不同,需要在延迟加载时注意单例实例的线程安全性,如果简单粗暴的实现,在多线程环境中将引起运行异常。

例如下面代码将引起运行异常:

package com.shixun.design.singleton;

public class  Singleton3  { 
    private static Singleton3 instance;

    private  Singleton3()  { 
    }

    public  static  Singleton3  getInstance()  { 
        if (instance == null) { 
            instance = new Singleton3();
        }
        return instance;
    }
}

上述代码多线程同时访问时可能会产生多个示例,甚至会破坏实例,违背单例的设计原则

用下面代码也能测试出来:

package com.shixun.design.singleton;

public class  SingletonTest  implements  Runnable{ 
    @Override
    public  void  run()  { 
        Singleton3 singleton3 =Singleton3.getInstance();
        System.out.println(singleton3);
    }

    public  static  void  main(String[] args)  { 
        for (int i=0;i<10;i++){ 
            SingletonTest myThread = new SingletonTest();
            Thread thread = new Thread(myThread, String.valueOf(i));
            Thread thread2 = new Thread(myThread, String.valueOf(i));
            Thread thread3 = new Thread(myThread, String.valueOf(i));
            thread.start();
            thread2.start();
            thread3.start();
        }
    }
}

Java单例模式之总有你想不到的知识

懒汉式多线程解决方案

synchronized

可以为返回单例实例的方法设置同步用来保证线程安全性

package com.shixun.design.singleton;

public class  Singleton4  { 
    private static Singleton4 instance;

    private  Singleton4()  { 
    }

    public  synchronized  static  Singleton4  getInstance()  { 
        if (instance == null) { 
            instance = new Singleton4();
        }
        return instance;
    }
}

这种写法能够在多线程中很好的工作,而且看起来它也具备很好的懒加载(lazy loading),但遗憾的是,由于整个方法被同步,因此效率相对较低

双检查锁方式

使用双检查锁需要进行两次instance == null的判断

  • 第一次判断没有锁,如果install不为null直接返回单实例对象,提高效率
  • 第二次判断防止多线程创建多个实例,假如A和B 两个线程同时争抢synchronized锁,A先争抢到锁,B 等待,A线程instance赋值实例化对象,释放锁,B线程获取到到锁,如果没有第二次判断的话,直接又会创建对象,那么就不符合单例要求

并且还需要为这个静态对象加上volatile关键字, volatile在这里的作用是:通知其他线程及时更新变量;保证有序性,禁止指令重排序。

通知其他线程及时更新变量还简单明了,第二个作用是这样的,我们举例说明一下:

原因举例说明: 在执行instance = new Singleton()语句时,一共是有三步操作的。

  1. 堆中分配内存
  2. 调用构造方法进行初始化
  3. 将instance引用指向内存地址。

在这三步有可能会产生指令重排序即有两种结果可能产生: 123与132(不管怎么重排序,单线程程序 的执行结果不会改变)

如果A线程执行到 instance = new Singleton() ,此时2 ,3发生重排序,选执行3,则instance已经不为 null,但是指向的对象还未初始化完成,如果此时B对象判断instance 不为null就会直接返回一个未初始 化完成的对象。

双检查锁方式代码如下:

package com.shixun.design.singleton;

public class  Singleton5  { 
    private volatile static Singleton5 instance;

    private  Singleton5()  { 
    }

    // 使用双检查锁方式
    public  static  Singleton5  getInstance()  { 
        if (instance == null) { 
            synchronized(Singleton5.class){ 
                if(instance == null){ 
                    instance = new Singleton5();
                }
            }
        }
        return instance;
    }
}

再测一下,没有问题:

package com.shixun.design.singleton;

public class  SingletonTest  implements  Runnable{ 
    @Override
    public void run() { 
        Singleton5 singleton5 =Singleton5.getInstance();
        System.out.println(singleton5);
    }

    public static void main(String[] args) { 
        for (int i=0;i<10;i++){ 
            SingletonTest myThread = new SingletonTest();
            Thread thread = new Thread(myThread, String.valueOf(i));
            Thread thread2 = new Thread(myThread, String.valueOf(i));
            Thread thread3 = new Thread(myThread, String.valueOf(i));
            Thread thread4 = new Thread(myThread, String.valueOf(i));
            Thread thread5 = new Thread(myThread, String.valueOf(i));
            thread.start();
            thread2.start();
            thread3.start();
            thread4.start();
            thread5.start();
        }
    }
}

Java单例模式之总有你想不到的知识

静态内部类

之前提到了,静态初始化将在实例被任何线程访问到之前对其进行初始化,因此,可以借助于这个特性对懒汉单例进行改造:

静态内部类加载机制:使用时候才被加载,而且多线程情况下, classloader能够保证只加载一份字节码

代码如下:

package com.shixun.design.singleton;

public class  Singleton6  { 

    private static class  SingletonHolder  { 
        private final static Singleton6 Instance = new Singleton6();
    }

    private  Singleton6()  { 
    }

    public  static  final  Singleton6  getInstance()  { 
        return SingletonHolder.Instance;
    }

    public  void  say()  { 
        System.out.println("调用了say方法");
    }

    public  static  void  sayHello()  { 
        System.out.println("调用了sayHello方法");
    }

测试也OK:

package com.shixun.design.singleton;

public class  SingletonTest  implements  Runnable{ 
    @Override
    public void run() { 
        Singleton6 singleton6 =Singleton6.getInstance();
        System.out.println(singleton6);
        singleton6.say();
    }

    public static void main(String[] args) { 
        for (int i=0;i<10;i++){ 
            SingletonTest myThread = new SingletonTest();
            Thread thread = new Thread(myThread, String.valueOf(i));
            Thread thread2 = new Thread(myThread, String.valueOf(i));
            Thread thread3 = new Thread(myThread, String.valueOf(i));
            Thread thread4 = new Thread(myThread, String.valueOf(i));
            Thread thread5 = new Thread(myThread, String.valueOf(i));
            thread.start();
            thread2.start();
            thread3.start();
            thread4.start();
            thread5.start();
        }
    }
}

Java单例模式之总有你想不到的知识

枚举(别瞎用)

JDK1.5之后引入了枚举,由于枚举的特性,可以利用其来实现单例,它不仅能避免多线程同步问 题,而且还能防止反序列化重新创建新的对象(序列化和反序列化后是同一个对象)

代码如下

package com.shixun.design.singleton;

public enum  Singleton7 { 
    INSTANCE;

    public  void  say(){ 
        System.out.println("say ni hello!");
    }
}

测试一下:

package com.shixun.design.singleton;

public class  SingletonTest  implements  Runnable{ 
    @Override
    public void run() { 
        Singleton7 singleton7 = Singleton7.INSTANCE;
        System.out.println(singleton7.hashCode());
        singleton7.say();
    }

    public static void main(String[] args) { 
        for (int i=0;i<10;i++){ 
            SingletonTest myThread = new SingletonTest();
            Thread thread = new Thread(myThread, String.valueOf(i));
            Thread thread2 = new Thread(myThread, String.valueOf(i));
            Thread thread3 = new Thread(myThread, String.valueOf(i));
            Thread thread4 = new Thread(myThread, String.valueOf(i));
            Thread thread5 = new Thread(myThread, String.valueOf(i));
            thread.start();
            thread2.start();
            thread3.start();
            thread4.start();
            thread5.start();
        }
    }

    /**
 * 生写懒汉式多线程问题
 */
    private static void method01() { 
        for (int i=0;i<100;i++){ 
            SingletonTest myThread = new SingletonTest();
            Thread thread = new Thread(myThread, String.valueOf(i));
            Thread thread2 = new Thread(myThread, String.valueOf(i));
            Thread thread3 = new Thread(myThread, String.valueOf(i));
            thread.start();
            thread2.start();
            thread3.start();
        }
    }
}

Java单例模式之总有你想不到的知识

点赞
收藏
评论区
推荐文章
3A网络 3A网络
2年前
Golang 常见设计模式之单例模式
之前我们已经看过了Golang常见设计模式中的装饰和选项模式,今天要看的是Golang设计模式里最简单的单例模式。单例模式的作用是确保无论对象被实例化多少次,全局都只有一个实例存在。根据这一特性,我们可以将其应用到全局唯一性配置、数据库连接对象、文件访问对象等。Go语言实现单例模式的方法有很多种,下面我们就一起来看一下。饿汉式饿汉式实现单例模式非
Wesley13 Wesley13
3年前
java设计模式1
1:单例模式简介  单例模式是一种常用的软件设计模式,它确保某个类只有一个实例,而且自行实例化并向整个系统提供唯一的实例。总而言之就是在系统中只会存在一个对象,其中的数据是共享的  特点:    单例类只能有一个实例,所以一般会用static进行修释。    单例类必须自己创建自己的唯一实例。也就是在类中要new一个自己。    单例类必
Wesley13 Wesley13
3年前
java 23种设计模式(五、单例模式)
作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。单例模式的结构  单例模式的特点:单例类只能有一个实例。单例类必须自己创建自己的唯一实例。单例类必须给所有其他对象提供这一实例。  饿汉式单例类publicclassEagerSingleton
红烧土豆泥 红烧土豆泥
4年前
创建型模式之单例设计模式
什么是单例设计模式?顾名思义,只有一个实例。单例模式它主要是确保一个类只有一个实例,并且可以提供一个全局的访问点。废话少说,直接上干货了单例模式之饿汉式所谓饿汉式,顾名思义,“它很饿”。所以说,它一旦被加载进来,就会直接实例化一个对象。例如:languageclassSingleton{privatestaticfin
Wesley13 Wesley13
3年前
JAVA设计模式之单例设计模式
    单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。  在JAVA中实现单例,必须了解JAVA内存机制,JAVA中实例对象存在于堆内存中,若要实现单例,必须满足两个条件:  1.限制类实例化对象。即只能产生一个对象。
Wesley13 Wesley13
3年前
Java单例模式
什么是单例模式  单例模式是在程序中,一个类保证只有一个实例,并提供统一的访问入口。为什么要用单例模式节省内存节省计算如对象实例中的一样的,那就不用每次都创建一个对象方便管理因为单例提供一个统一的访问入口,不需要创建N多个对象,很多工具类都用了单例实现,如日志、字符串工具类
Wesley13 Wesley13
3年前
PHP单例模式(精讲)
首先我们要明确单例模式这个概念,那么什么是单例模式呢?单例模式顾名思义,就是只有一个实例。作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类我们称之为单例类。单例模式的要点有三个:一是某个类只能有一个实例;二是它必须自行创建这个实例;三是它必须自行向整个系统提供这个实例
Stella981 Stella981
3年前
C#设计模式(1)——单例模式(Singleton)
单例模式即所谓的一个类只能有一个实例,也就是类只能在内部实例一次,然后提供这一实例,外部无法对此类实例化。单例模式的特点:1、只能有一个实例;2、只能自己创建自己的唯一实例;3、必须给所有其他的对象提供这一实例。普通单例模式(没有考虑线程安全)  ///<summary///单例模式
Wesley13 Wesley13
3年前
(面试常问)4种单例设计模式的总结(内含代码以及分析)
单例设计模式:  单例模式,是一种常见的软件设计模式.在它的核心结构中只包含了一个被称为单例的特殊类.通过单例模式可以保证系统中只有该类的一个实例对象.优点:  实例控制:单例模式会阻止其它对象实例化其自己的单例对象的副本,从而确保所有对象都访问的是唯一的实例   灵活性:因为类控制了实例化过程,所以类可以很灵活的更改实
Wesley13 Wesley13
3年前
Java设计模式:Singleton(单例)模式
概念定义Singleton(单例)模式是指在程序运行期间,某些类只实例化一次,创建一个全局唯一对象。因此,单例类只能有一个实例,且必须自己创建自己的这个唯一实例,并对外提供访问该实例的方式。单例模式主要是为了避免创建多个实例造成的资源浪费,以及多个实例多次调用容易导致结果出现不一致等问题。例如,一个系统只能有一个窗口管理器或文件系统,一个程
Wesley13 Wesley13
3年前
23种设计模式(1):单例模式
定义:确保一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。类型:创建类模式类图:!23种设计模式(1):单例模式第1张|快课网(http://static.oschina.net/uploads/img/201407/05200605_0dij.gif"23种设计模式(1):单例模式
Network
Network
Lv1
给不了彼此幸福的人是终身都不必再见的人.
文章
4
粉丝
0
获赞
0