单例模式
单例对象(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
这就是单例模式的懒汉式和恶汉式常用的创建方式,关于单例模式还有很多复杂的用法跟可能出现的问题,需要慢慢去摸索,最后推荐一个jdk中用到的单例模式,java.lang包下的Runtime类,一个单例模式的使用。