java单例模式
java单例模式是一种常见的设计模式,单例模式分为3种
- 懒汉式单例
- 饿汉式单例
- 登记式单例
单例模式的特点
- 单例类只能有一个实例
- 单例类必须自己创建自己的唯一实例
- 单例类必须给所有其他对象提供这一实例
线程安全的概念
线程安全就是指的是如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能同时运行这段代码,如果每次运行结果和单线程运行结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。
几种单例模式的实现与比较(懒汉,饿汉,登记)
1.懒汉式单例,先上代码(只适用于单线程,不好)
/**
*
* <p>description:懒汉,线程不安全</p>
* @author AbnerLi
* @date 2017年10月12日上午10:56:50
*/
public class LazySingleton {
private static LazySingleton lazySingleton = null;
/**
* 这里的构造方法设计成为private 就是防止外部对这种单例进行实例化。(其实也能进行实例化,就是通过java反射机制,可以修改访问权限)
*/
private LazySingleton(){
}
/**
* <p>description:获取实例的方法</p>
* @author AbnerLi
* @date 2017年10月12日上午11:33:26
* @return
*/
public static LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
上面就是懒汉式单例,这种写法lazy loading很明显,但是致命的是在多线程不能正常工作,是线程不安全的,当多线程的时候,两个线程同时运行到判断lazySingleton是否为空的if语句,并且lazySingleton确实没有被创建好,那么两个线程都会创建一个实例。经过改进,将上面获取实例的方法加上上关键字synchornized,可以使得在多线程环境下运行。
在方法上面加上synchornized关键字进行同步
public static synchronized LazySingleton getInstance(){
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
但是上面这种加synchronized关键字进行同步也不是很好,原因就是,每次通过getInstance方法取得lazySingleton实例的时候当前线程都会试图去获取一个同步锁。Be known to us(众所周知),加锁是一个非常耗时间和性能的,所以如果能够想到更好的办法,能避免就避免。懒汉式还可以进过改进得到如下稍微可行的方式。
双重检查锁定
public static LazySingleton getInstance(){
if(lazySingleton == null){//如果为空才去获取同步锁
synchronized (LazySingleton.class) {
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
上面这种方式优化了懒汉式单例,只有当lazySingleton为null的时候采取获取同步锁,创建一次实例。当实例被创建,后面就不需要试图加锁了。但是上面获取实例都需要进行加锁的,有没有不需要加锁的呢?当然有
静态内部类
/**
* 静态内部类,效率最高,不用同步
*/
private static class LazyHolder{
private static final LazySingleton instance = new LazySingleton();
}
public static final LazySingleton getStaticClassLazySingleton(){
return LazyHolder.instance;
}
这种方式比上面所有方式好一些,即实现了线程安全,同时又避免了同步带来的性能影响。
2.饿汉式单例(建议使用)
先上代码
/**
*
* <p>description:饿汉式单例类,在类的初始化时就自行实例化了,因为java的静态变量,在类的加载的时候实例化</p>
* @author AbnerLi
* @date 2017年10月13日上午9:44:35
*/
public class HungrySingleton {
//私有的构造方法
private HungrySingleton(){
}
//自行实例化
private static final HungrySingleton instance = new HungrySingleton();
//静态工厂方法
public static HungrySingleton getInstance(){
return instance;
}
}
饿汉式单例,采用的式静态属性,因为在java中类中的静态成员会随着类的加载而加载。因此可以直接调用HungrySingleton
3.登记式单例(建议使用)
/**
*
* <p>description:登记式单例,类似于Spring里面的方法,将类名注册,下次从里面直接获取</p>
* @author AbnerLi
* @date 2017年10月13日上午10:28:53
*/
public class SignInSingleton {
private static Map<String, SignInSingleton> map = new HashMap<String, SignInSingleton>();
/**
* 静态块,将实例放入map中
*/
static{
SignInSingleton instance = new SignInSingleton();
map.put(instance.getClass().getName(), instance);
}
//保护的默认构造方法
protected SignInSingleton() {
// TODO Auto-generated constructor stub
}
//静态工厂方法,返还此类唯一的实例
public static SignInSingleton getInstance(String name){
//获取完整类名
if(name == null){
name = SignInSingleton.class.getName();
System.out.println("name == null" + "---->name=" + name);
}
//map中没有实例,就创建一个
if(map.get(name) == null){
try {
map.put(name, (SignInSingleton)Class.forName(name).newInstance());
} catch (Exception e) {
e.printStackTrace();
}
}
return map.get(name);
}
//一个示意性的方法
public String about(){
return "Hello , I am RegSingleton.";
}
public static void main(String [] args){
SignInSingleton signInSingleton = SignInSingleton.getInstance(null);
System.out.println(signInSingleton.about());
}
}
登记式单例事实上式维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有等级的,先登记然后再返回。