Java之23种设计模式-单例模式

单例模式

       单例对象(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类,一个单例模式的使用。

点赞