随笔分类
单例模式
保证一个类只生成唯一的实例对象,同时能够提供能对该实例加以访问的全局访问方法
在应用系统开发中,常有以下需求:
- 在多线程之间,比如servlet环境下,共享同一个资源或者操作同一个对象
- 在整个程序空间使用全局变量,共享资源
- 大规模系统中,为了性能的考虑,需要减少对象的创建时间等等
这时候单例模式便派上用场了
如何来实现单例模式呢
现在看看笔者最初的想法
public class SingletonPattern {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//构造函数私有化
private SingletonPattern(){
}
//采用全局静态方法 无this 以自己的实例为返回值的静态的公有方法,静态工厂方法
public static SingletonPattern getSingletonPattern(){
return new SingletonPattern();
}
}
public class Main {
public static void main(String[] args) {
/**
* 构造函数公有,无穷实例可以创建
*
SingletonPattern singletonPattern = new SingletonPattern();
SingletonPattern singletonPattern2 = new SingletonPattern();
singletonPattern.setName("liangye");
singletonPattern2.setName("luoye");
System.out.println(singletonPattern.getName()); //liangye
System.out.println(singletonPattern2.getName()); //luoye
*/
/**
* 可见单单构造函数私有化和提供一个全局可以访问的静态方法
* 并不能保证类实例的唯一
SingletonPattern singletonPattern = SingletonPattern.getSingletonPattern();
singletonPattern.setName("liangye");
System.out.println(singletonPattern.getName()); //liangye
*/
}
}
懒汉式单例
用时再创建
实现思路:在调用该类的全局静态方法时加上条件判断即可(第二次引用的对象不为空)
/**
* 懒汉式
*/
public class SingletonPatternLazy {
private String name;
private static SingletonPatternLazy singletonPatternLazy;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//构造函数私有化
private SingletonPatternLazy(){
}
//采用全局静态方法 无this 以自己的实例为返回值的静态的公有方法,静态工厂方法
public static SingletonPatternLazy getSingletonPattern(){
if (singletonPatternLazy == null) //如果对象已经被引用过,则直接返回之前的引用即可,保证了唯一性
singletonPatternLazy = new SingletonPatternLazy();
return singletonPatternLazy;
}
}
public class SingletonPatternLazyTest {
public static void main(String[] args) {
SingletonPatternLazy singletonPatternLazy = SingletonPatternLazy.getSingletonPattern();
SingletonPatternLazy singletonPatternLazy2 = SingletonPatternLazy.getSingletonPattern();
singletonPatternLazy.setName("liangye");
singletonPatternLazy2.setName("luoye");
System.out.println(singletonPatternLazy.getName()); //luoye
System.out.println(singletonPatternLazy2.getName()); //luoye
}
}
可以看出,单例实例被延迟加载,即只有在真正使用的时候才会实例化一个对象并交给自己的引用
但是
懒汉式单例是线程不安全的
怎么理解?
java中new一个对象并不是一个原子操作
- 当JVM遇到一条new指令时,首先会去检查这个指令的参数是否能在常量池中定位到一个类的符号引用,并且检查这个符号引用代表着的类是否被加载、解析、初始化过,如果没有,那必须先执行相应的类加载过程
- 在类加载检查通过之后,接下来JVM会为新生的对象分配内存,对象所需的内存大小在类加载后便可以完全确定下来,为新生对象分配内存实则上就是从堆中划分出一块固定大小的内存
- 分配完内存后,再进行对象的默认初始化和显示初始化
这也便意味着在懒汉式单例中,在多线程环境下,有可能一个对象还没有new完,另一个线程已经进入,这也便不能保证了类实例的唯一性了
可有的解决方案:
使用同步 synchronized 保证了原子性以及线程执行的有序性 ,其实还有可见性(之后或有介绍)
//采用全局静态方法 无this , 多线程下不安全,使用线程同步
public static synchronized SingletonPatternLazy getSingletonPattern(){
if (singletonPatternLazy == null) //如果对象已经被引用过,则直接返回之前的引用即可,保证了唯一性
singletonPatternLazy = new SingletonPatternLazy();
return singletonPatternLazy;
}
但
缺点也很明显
多线程下显著降低了效率
怎么理解?
对于判断该实例是否为空,只有在第一次时需要同步,但是之后便不需要了,但是加了同步,每个线程都需要等待并去判断,显然高并发下是难以容忍的延迟损耗
饿汉式单例
不管用不用,先创建先
虽然是浪费资源的
多线程里是安全的
- 因为只有一份,且返回的始终是一开始创建的那一个
思路:
将类的实例变为成员变量:私有的常量,调用静态方法时把实例的引用返回
因为实例是常量,便意味着引用不可改变(只有一份),但是引用里面的数据是可以改变的,加上构造私有化,这便限制了类实例的数目只有一份
代码实现
/**
* 饿汉式
*/
public class SingletonPatternHungry {
private final static SingletonPatternHungry SINGLETON_PATTERN_HUNGRY = new SingletonPatternHungry();
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//构造函数私有化
private SingletonPatternHungry(){
}
//采用全局静态方法 无this
public static SingletonPatternHungry getSingletonPatternHungry(){
return SINGLETON_PATTERN_HUNGRY; //第一次加载这个类是便会把常量的引用返回,引用不可改变(只有一份),但是引用里的数据可以被改变
}
}
public class Main {
public static void main(String[] args) {
SingletonPattern singletonPattern = SingletonPattern.getSingletonPattern();
SingletonPattern singletonPattern2 = SingletonPattern.getSingletonPattern();
singletonPattern.setName("liangye");
singletonPattern2.setName("luoye");
System.out.println(singletonPattern.getName()); //luoye
System.out.println(singletonPattern2.getName()); //luoye
}
}
双重检查
可以认为是优化后的懒汉式线程(单例实例同样被延迟加载)
代码实现
/**
* 双重检查实现
* 外层判断可以认为是针对以后线程判断使用的
* 内层判断是针对第一次线程判断使用的
* @return
*/
public static SingletonPatternLazy getSingletonPattern(){
if (singletonPatternLazy == null) { // 第一次检查 有提高效率之能 避免了线程的等待
synchronized (SingletonPatternLazy.class){ //外层判断保证了此方法只会被成功执行一次
if (singletonPatternLazy == null) // 第二次检查 保证单例逻辑(一开始多线程已经通过第一个判断了) 简单的判断
singletonPatternLazy = new SingletonPatternLazy(); //只会执行一次
}
}
return singletonPatternLazy;
}