单例模式
单例模式
1.概念
-
单例模式(Singleton Pattern)指一个类只有一个实例,并且该类能自行创建这个实例的一种模式。
例如,Windows 中只能打开一个任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,或出现各个窗口显示内容的不一致等错误。
-
-
注意:
- 单例模式只能有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类对外提供一个访问该单例的全局访问点
2.优缺点
- 缺点:
- 单例模式一般没有接口,扩展困难。如要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则。
- 在并发测试中,单例模式不利于代码的调试。在调试过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象。
- 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责原则。
- 优点:
- 单例模式可以保证内存里只有一个实例,减少了内存的开销。
- 可以避免对资源的多重占用
- 单例模式设置全局访问点,可以优化和共享资源的访问
3.单例模式的应用场景
4.单例模式的实现
为了保证类的实例在运行的时候只有一个,我们首先要将构造器私有化、然后将单例对象保存在该类的静态变量中,我们可以通过getInstance()方法获取对象。
Singleton 模式通常有两种实现形式。
-
1.懒汉
//该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。代码如下: public class LazySingleton{ //保证instance在所有线程中同步 private static volatile LazySingleton instance = null; private LazySingleton(){ } public static synchronized LazySingleton getInstance(){ if(instance == null){ instance = new LazySingleton(); } return instance; } }
注意:如果编写的是多线程程序,则不要删除上例代码中的关键字 volatile 和 synchronized,否则将存在线程非安全的问题。如果不删除这两个关键字就能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。
-
2.饿汉
//该模式的特点是类一旦加载就创建一个单例,保证在调用 getInstance 方法之前单例已经存在了 public class HungrySingleton{ private static final HungrySingleton instane = new HungrySingleton(); private HungrySingleton(){ } public static HungrySingleton getInstance(){ return instance; } }
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
5.DCL(Double Check Lock双端检索机制)
但其性能不高,因为每次访问这个方法的时候都需要执行同步操作
public class Singleton { private static volatile Singleton singleton; private Singleton() { } public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
-
加入volatile修饰Singleton,是可以保证可见性和禁止指令重排
-
不安全原因:
singleton = new Singleton() ,它并非是一个原子操作
-
某个线程执行到第一次检测,读取到的instance不为null时,instance的引用对象可能还没有完成初始化。
-
instance = new SingletonDemo();可以分为以下3步完成
-memory = allocate(); //1.分配对象内存空间
-instance(memory); //2.初始化对象
-instance = memory; //3.设置instance指向刚分配的内存地址,此时instance != null
-不存在数据依赖关系
-
-
第一个if判断是为了减少每次都要上锁的开销,增加性能
-
第二个判断是:当两个线程同时进入get方法,这时两个线程都判断singleton为空,这时候,其中一个线程会进入synchronized方法,而另一个线程在等待。等到第一个进入的线程执行完毕后,如果没有第二个判断,该线程便会再创建一个实例,违背了单例的规则
-
5.单例模式的应用实例
-
1.用懒汉式单例模式模拟产生美国当今总统对象。
-
分析:在每一届任期内,美国总统只有一人,所以本实例适合用单例模式实现,如图为懒汉式单例实现的结构图
-
-
public class SingletonLazy{ public static void main(String[] args){ President zt1 = President.getInstance(); zt1.getName(); President zt2 = President.getInstance(); zt2.getName(); if(zt1 == zt2){ System.out.println("他们是同一个人"); }else{ System.out.println("他们不是同一个人"); } } } class President{ private static volatile President instance = null; private President(){ System.out.println("产生一位总统"); } public static synchronized President getInstance(){ if(instance == null){ instance = new President(); }else{ System.out.println("已经产生一位总统,不能产生新的总统"); } return instance; } public void getName(){ System.out.println("我是美国总统"); } } //************************************************ public class Singleton { // volatile 保证可见性和禁止指令重排序 private static volatile Singleton singleton; public static Singleton getInstance() { // 第一次检查 if (singleton == null) { // 同步代码块 synchronized(this.getClass()) { // 第二次检查 if (singleton == null) { // 对象的实例化是一个非原子性操作 singleton = new Singleton(); } } } return singleton; } } /** 上面代码中, new Singleton() 是一个非原子性操作,对象实例化分为三步操作:(1)分配内存空间,(2)初始化实例,(3)返回内存地址给引用。所以,在使用构造器创建对象时,编译器可能会进行指令重排序。假设线程 A 在执行创建对象时,(2)和(3)进行了重排序,如果线程 B 在线程 A 执行(3)时拿到了引用地址,并在第一个检查中判断 singleton != null 了,但此时线程 B 拿到的不是一个完整的对象,在使用对象进行操作时就会出现问题。 所以,这里使用 volatile 修饰 singleton 变量,就是为了禁止在实例化对象时进行指令重排序。 **/
-
2.用饿汉式单例模式模拟产生猪八戒对象。
import java.awt.*; import javax.swing.*; public class SingletonEager { public static void main(String[] args) { JFrame jf = new JFrame("饿汉单例模式测试"); jf.setLayout(new GridLayout(1, 2)); Container contentPane = jf.getContentPane(); Bajie obj1 = Bajie.getInstance(); contentPane.add(obj1); Bajie obj2 = Bajie.getInstance(); contentPane.add(obj2); if (obj1 == obj2) { System.out.println("他们是同一人!"); } else { System.out.println("他们不是同一人!"); } jf.pack(); jf.setVisible(true); jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } } class Bajie extends JPanel { private static Bajie instance = new Bajie(); private Bajie() { JLabel l1 = new JLabel(new ImageIcon("src/Bajie.jpg")); this.add(l1); } public static Bajie getInstance() { return instance; } }
-
6.单例模式的扩展
- 单例模式可扩展为有限的多例模式(Multitcm),这种模式可生成有限个实例并保存在ArrayList中,客户需要时可随机获取