单例模式
单例对象(Singleton)是一种常用的设计模式。在 Java 应用中,单例对象能保证在一个 JVM 中,该对象只有一个实例存在。这样的模式有几个好处:
1、某些类创建比较频繁,对于一些大型的对象,这是一笔很大的系统开销。
2、省去了 new 操作符,降低了系统内存的使用频率,减轻 GC 压力。
3、有些类如交易所的核心交易引擎,控制着交易流程,如果该类可以创建多个的话,系统完全 乱了。(比如一个军队出现了多个司令员同时指挥,肯定会乱成一团),所以只有使用单例模式, 才能保证核心交易服务器独立控制整个流程。
1)饿汉式
单例模式中的饿汉式是在类加载的时候就已经初始化了:如下:
Singleton.java
package com.lxj.singleton; public class Singleton { /* 私有构造方法,防止被实例化 */ private Singleton() { } /* 持有私有静态实例,防止被引用,此处赋值为 null,目的是实现延迟加载 */ private static Singleton instance = new Singleton(); /* 静态工程方法,创建实例 */ public static Singleton getInstance() { return instance; } public static void main(String[] args) { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1 == instance2); } }
运行结果:
true
getInstance()方法获取的实例对象的引用是相同的,说明他们都指向堆空间相同的引用。
2)懒汉式
Singleton.java
package com.lxj.singleton; //懒汉式:在需要获取对象时才new对象,堆空间分配内存 //存在线程安全问题 public class Singleton { /* 私有构造方法,防止被实例化 */ private Singleton() {} /* 持有私有静态实例,防止被引用,此处赋值为 null,目的是实现延迟加载 */ private static Singleton instance = null; /* 静态工程方法,创建实例 */ public static Singleton getInstance() { if(instance == null) { instance = new Singleton(); } return instance; } public static void main(String[] args) { Singleton instance1 = Singleton.getInstance(); Singleton instance2 = Singleton.getInstance(); System.out.println(instance1 == instance2); } }
运行结果:
true
从上面的结果看,的确是单例模式,但是在多线程的环境下可能因为在刚刚要执行new Singleton()对象时,CPU切换到另外的线程,导致后面的线程同样进入if(instanfce == null) 的语句。导致创建了不同的对象。所以我们应该考虑使用同步。
package com.lxj.singleton; //懒汉式:在需要获取对象时才new对象,堆空间分配内存 //存在线程安全问题 public class Singleton implements Runnable{ private Singleton() {} private static Singleton instance = null; public static Singleton getInstance() { if(instance == null) { try { //这里停顿是为了放大出现线程安全 Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Singleton(); } return instance; } @Override public void run() { for(int i = 0 ; i < 10 ; i++) { Singleton instance2 = Singleton.getInstance(); System.out.println(Thread.currentThread().getName()+instance2); } } public static void main(String[] args) { Singleton singleton = new Singleton(); Thread thread = new Thread(singleton); Thread.currentThread().setName("主线程: "); thread.setName("副线程: "); thread.start(); for(int i = 0 ; i < 10 ; i++) { Singleton instance2 = Singleton.getInstance(); System.out.println(Thread.currentThread().getName()+instance2); } } }
运行结果:
副线程: com.lxj.singleton.Singleton@24bd02ff 副线程: com.lxj.singleton.Singleton@24bd02ff 副线程: com.lxj.singleton.Singleton@24bd02ff 副线程: com.lxj.singleton.Singleton@24bd02ff 副线程: com.lxj.singleton.Singleton@24bd02ff 副线程: com.lxj.singleton.Singleton@24bd02ff 主线程: com.lxj.singleton.Singleton@7852e922 //这里的hashCode的十六进制码不同,说明出现问题了 主线程: com.lxj.singleton.Singleton@24bd02ff 主线程: com.lxj.singleton.Singleton@24bd02ff 主线程: com.lxj.singleton.Singleton@24bd02ff 主线程: com.lxj.singleton.Singleton@24bd02ff 主线程: com.lxj.singleton.Singleton@24bd02ff 主线程: com.lxj.singleton.Singleton@24bd02ff 副线程: com.lxj.singleton.Singleton@24bd02ff 主线程: com.lxj.singleton.Singleton@24bd02ff 副线程: com.lxj.singleton.Singleton@24bd02ff 主线程: com.lxj.singleton.Singleton@24bd02ff 副线程: com.lxj.singleton.Singleton@24bd02ff 副线程: com.lxj.singleton.Singleton@24bd02ff 主线程: com.lxj.singleton.Singleton@24bd02ff
现在考虑加synchronized关键字
public static synchronized Singleton getInstance() { if(instance == null) { try { Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Singleton(); } return instance; }
运行结果:
副线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922
但是,synchronized 关键字锁住的是这个对象,这样的用法,在性能上会有所下降,因为 每次调用 getInstance(),都要对对象上锁,事实上,只有在第一次创建对象的时候需要加 锁,之后就不需要了,所以,这个地方需要改进。我们改成下面这个:
public static Singleton getInstance() { if (instance == null) { //如果后面的线程发现为空才进到这里,实例化后,就不会往下执行了 synchronized (Singleton.class) { if (instance == null) { try { Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } instance = new Singleton(); } } } return instance; }
运行结果: 主线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 主线程: com.lxj.singleton.Singleton@7852e922 副线程: com.lxj.singleton.Singleton@7852e922