单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证应用中一个单例类只有且只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并提供全局访问点。
1. 实现单例三要素
教科书中的示例是很简单的,要想实现一个单例类,只需要三点:
1、定义私有的静态成员
2、私有化构造方法
3、提供公有的静态工厂方法
2. 单例的种类(饿汉模式/懒汉模式)
饿汉模式:类加载的时候都会去创建实例;
懒汉模式:只在外部对象第一次请求实例的时候才去创建,如果没有就立即创建一个对象,然后返回,如果已有对象就不再创建,立即返回。
3. 两种模式的区别
懒汉模式在类加载的时候比较快,获取对象比较慢;
饿汉模式在类加载的时候比较慢,获取对象比较快;
注: 饿汉模式是线程安全的,而懒汉模式是线程不安全的
4.单例两种类型的实现
饿汉式单例
package com.wxl.singleton;
/**
* @author wxl
* @remark 单例模式--饿汉式实现
* 作用:保证整个应用程序中某个实例有且只有一个
* 类型:饿汉模式/懒汉模式
* @version 1.0
*/
public class SingletonEager {
/**
* 创建类的唯一实例
* 类加载时即创建
*/
private static SingletonEager instance = new SingletonEager();
/**
* 将构造方法私有化,不允许外部直接创建对象
*/
private SingletonEager(){
}
/**
* 提供一个外部可以访问的获取实例方法
* @return
*/
public static SingletonEager getInstance(){
return instance;
}
}
懒汉式单例
package com.wxl.singleton;
/**
* @author wxl
* @remark 单例模式--懒汉式实现
* 作用:保证整个应用程序中某个实例有且只有一个
* 类型:饿汉模式/懒汉模式
* @version 1.0
*/
public class SingletonLazy {
/**
* 创建类的唯一实例
*/
private static SingletonLazy instance = null;
/**
* 将构造方法私有化,不允许外部直接创建对象
*/
private SingletonLazy(){
}
/**
* 提供一个外部可以访问的获取实例方法
* @return
*/
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy ();
}
return instance;
}
}
5. 单例模式线程测试类
package com.wxl.singleton;
public class SingletonThreadSecurity implements Runnable {
@Override
public void run() {
// 饿汉模式
SingletonEager se = SingletonEager.getInstance();
System.out.println("SingletonEager hashCode is :" + se.hashCode());
// 懒汉模式
SingletonLazy sl = SingletonLazy.getInstance();
System.out.println("SingletonLazy hashCode is :" + sl.hashCode());
}
}
6. 单例模式单元测试类
package com.wxl.singleton;
import static org.junit.Assert.*;
import org.junit.Test;
public class SingletonTest {
/**
* 测试饿汉模式
*/
@Test
public void testSingletonEager() {
SingletonEager se1 = SingletonEager.getInstance();
SingletonEager se2 = SingletonEager.getInstance();
System.out.println(se1.hashCode());
System.out.println(se2.hashCode());
assertEquals(se1, se2);
}
/**
* 测试懒汉模式
*/
@Test
public void testSingletonLazy(){
SingletonLazy sl1 = SingletonLazy.getInstance();
SingletonLazy sl2 = SingletonLazy.getInstance();
System.out.println(sl1.hashCode());
System.out.println(sl2.hashCode());
assertEquals(sl1, sl2);
}
/**
* 测试线程安全
*/
@Test
public void testThreadSecurity(){
SingletonThreadSecurity sts = new SingletonThreadSecurity();
Thread t1 = new Thread(sts);
t1.start();
Thread t2 = new Thread(sts);
t2.start();
}
}
7.测试结果
饿汉模式测试
懒汉模式测试
饿汉模式与懒汉模式多线程测试
测试结论:经过大量的实验,多线程情况下懒汉模式实现的单例所得到的实例不是唯一的,而饿汉模式得到的实例始终是同一个;
8. 懒汉模式线程不安全解决方案
/**
* 线程同步锁
*/
private static Object synchrolock = new Object();
/**
* 提供一个外部可以访问的获取实例方法
* @return
*/
public static SingletonLazy getInstance() {
synchronized (synchrolock) {
// 不存在时才实例化
if (instance == null) {
instance = new SingletonLazy();
}
}
return instance;
}
9. 内存回收
单例的内存问题也是值得关注的,一量单例创建以后,静态变量instance就会持有一份内存引用,而且由于其static性质,这份内存将在程序运行期间持续占用,无法通过GC进行回收。所以对内存敏感的程序要减少对单例的使用,或者妥善处理内存回收问题。
10. 小结
总之,虽然单例看上去很美,但还是有这么多需要认真思考、妥善决策的技术要点,用好单例并不像我们在教科书中看到的那样简单。