JAVA编程的单例模式(Singleton Pattern)遇到多线程

单例模式,也叫单子模式,是一种常用的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。

实现单例模式的思路是:一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。

单例模式在多线程的应用场合下必须小心使用。如果当唯一实例尚未创建时,有两个线程同时调用创建方法,那么它们同时没有检测到唯一实例的存在,从而同时各自创建了一个实例,这样就有两个实例被构造出来,从而违反了单例模式中实例唯一的原则。 解决这个问题的办法是为指示类是否已经实例化的变量提供一个互斥锁(虽然这样会降低效率)。

通常单例模式在Java语言中,有两种构建方式:饿汉方式(Eager Initialization)和懒汉方式(Lazy Initialization)。

一、饿汉方式(Eager Initialization)

package com.xieyincai.designpattern;

public class Singleton {

	private final static Singleton INSTANCE = new Singleton();
	
	private Singleton(){}
	
	public static Singleton getInstance() {
		return INSTANCE;
	}
}

加载类的时候就实例化,而且构造函数是私有的,保证了此实现方式是线程安全的。

二、懒汉方式(Lazy Initialization)

懒汉方式(Lazy Initialization)在多线程环境下,为了保证线程安全,可以有以下几种方法:

1、对getInstance()方法使用关键字synchronized

package com.xieyincai.designpattern;

public class Singleton {

	private static volatile Singleton INSTANCE = null;
	
	private Singleton(){}
	
	public synchronized static Singleton getInstance() {
		
		if(INSTANCE == null) INSTANCE = new Singleton();
		return INSTANCE;
	}
}

getInstance()方法用关键字synchronized同步,保证线程安全,但是对整个方法加锁,并发性不好,从而效率不高。

2、使用双重检查锁(double-checked locking)

package com.xieyincai.designpattern;

public class Singleton {

	private static volatile Singleton INSTANCE = null;
	
	private Singleton(){}
	
	public static Singleton getInstance() {
		// 第一次检查
		if(INSTANCE == null) {
			synchronized(Singleton.class) {
				// 第二次检查
				if(INSTANCE == null) INSTANCE = new Singleton();
			}
		}
		return INSTANCE;
	}
}

使用双重检查锁(double-checked locking)机制,成功解决了懒汉方式(Lazy Initialization)多线程安全性问题。

3、构建静态内部类SingletonHolder

package com.xieyincai.designpattern;

public class Singleton {

	private static class SingletonHolder {
		private static final Singleton INSTANCE = new Singleton();
	}
	
	private Singleton(){}
	
	public static Singleton getInstance() {
		return SingletonHolder.INSTANCE;
	}
}

由于SingletonHolder是私有的,除了getInstance()方法之外没有办法访问它,确保线程安全。私有内部类SingletonHolder貌似是饿汉方式(Eager Initialization),在加载类时就实例化了,有意思啊。

Leave a Reply

Your email address will not be published. Required fields are marked *