设计模式-单例
设计模式-单例模式
一、概述
单例模式在框架中是比较常见的,比如大名鼎鼎的Spring框架中,会默认把bean设计成单例。那Spring集成Spring MVC、Struts2等也都是单例的,因为只要把创建对象的权利交给Spring来管理后,那么就可以通过注入的方式来获取实例对象。
单例的概念:创建类在当前进程中只有一个实例。简单来说就是一个类只有一个实例。
二、编写单例模式代码
编写单例模式三大步骤:
1.构造函数私有化
2.类的内部创建实例
3.对外提供方法
1.饿汉式
public class Singleton{
private statice singleton = new Singleton();
private Singleton(){
}
public Singleton getInstence() {
return singleton;
}
}
饿汉式基本上是按照三大步骤来写出来的,但是我们仔细观察发现会有瑕疵。就是不管我们有没有调用getInstence()方法,这个类都被new出来了。我们知道new 一个对象会在堆中开辟空间,那堆属于jvm内存模型的一类,那显然是要消耗内存的。我们第一反应应该是根据我们学习的懒加载来实现进一步优化。所谓懒加载也就是在方法调用的时候再创建对象。
2.懒汉式
public class Singleton{
private statice singleton ;
private Singleton(){
}
public Singleton getInstence() {
if(singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
这样就做到了一个简单优化,所以名字也就不一样了。此时我们叫做懒汉式。我们再仔细想想,懒汉式有什么缺点?
public class TestSingleton implements Callable<String> {
@Override
public String call() throws Exception {
Singleton03 singleton03 = Singleton03.getSingleton03();
return singleton03.toString();
}
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newFixedThreadPool(10);
List<Future<String>> callableList = new ArrayList<Future<String>>();
for (int i = 0; i < 100; i++) {
Future<String> submit = executorService.submit(new TestSingleton());
callableList.add(submit);
}
try {
for (Future<String> future : callableList) {
System.out.println(future.get());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果:
singleton.Singleton03@7f82d215
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@478899a9
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
根据结果观察:我们的单例模式还是有瑕疵。在多线程环境下,我调用getSingleton03()方法,但是明显打印出来的地址是有不一样的。也就是我们我们在多线程下创建100次对象,无法保证每次都是同一个实例。我们可以想想其中一种情况,当我们线程1走到判断singleton是不是为空的时候,线程2正准备创建实例了,那线程1判断这个singleton还是为null,那我也会创建一个实例。所以创建出来的实例相当于new了两次。我们可以得知,多线程下这种简单的懒汉式是有瑕疵的,是无法保证线程安全的。我们应该继续对单例进行优化。
怎么优化呢?我们首先想到synchronized关键字。
public class Singleton {
//声明变量
private static Singleton singleton = null;
//私有化构造参数
private Singleton(){
}
//对外提供方法
public static synchronized Singleton getSingleton(){
if (singleton == null){
singleton = new Singleton();
}
return singleton;
}
}
那我们来测试验证一下结果:
singleton.Singleton03@7f82d215
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@b16e737
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
singleton.Singleton03@6334f86d
结果显示,加上synchronized结果并不是那么如人意。此时我们意识到,给整个方法加锁不仅导致无法保证线程安全,而且效率还低下。
public class Singleton {
//声明变量
private static volatile Singleton02 singleton = null;
//私有化构造参数
private Singleton02(){
}
//对外提供方法
public static Singleton getSingleton(){
if (singleton == null){
synchronized (Singleton.class){
if (singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
单例的双锁机制,我们会思考一下为什么需要volatile关键字。首先我们想一下volatile关键字作用有哪些:
1.保证线程之间可见性。一个线程修改变量,可以强制刷新到主内存,保证线程和主线程之间的变量是一致的。
2.禁止指令重排。什么叫指令重排序,由于机器的自身优化功能,有些程序代码会优先执行,也就是计算机指令会进行一个优先级排序,再按照这个顺序执行,很有可能导致程序出错。
在单例设计模式中,我们可以看到volatile的作用是第二层:即禁止指令重排。除了上面这些,当然还有其他两种单例模式:静态内部类懒汉式、枚举。我们可以一一来学习一遍。
3.静态内部类懒汉式
public class Singleton {
private Singleton() {
}
// 创建静态内部类
private static class InnerClass {
private static final Singleton singleton = new Singleton();
}
// 对外提供方法
public static final Singleton getSingleton() {
return InnerClass.singleton;
}
}
4.枚举
public enum Singleton {
Singleton;
public Singleton getSingleton() {
return Singleton;
}
}

浙公网安备 33010602011771号