Java懒汉式单例模式详解

单例模式最常见的有两种,饿汉式和懒汉式两种。本文先简单说一下懒汉式单例,再着重叙述饿汉式单例,饿汉式单例是本文的叙述重点。

懒汉式的优点是:写起来比较简单,而且不存在多线程同步问题,避免了synchronized所造成的性能问题;缺点是:初始化类的时候就需要构造实例,(即便你还没有用到这个实例),因此在某些特定条件下会耗费内存。懒汉式的写法如下:

 1 /**
 2  * 懒汉式单例
 3  * @author James Chong
 4  *
 5  */
 6 public class SingleTon {
 7
 8     //加载类的时候会初始化static的instance,从这以后,这个static的instance对象便一直占着这段内存,永远不会被回收掉。
 9     private static SingleTon instance = new SingleTon();
10
11     private SingleTon(){//将构造函数private掉,避免直接new SingleTon()
12         super();
13     }
14
15     /**
16      * 因为是单例,所以只能通过static方法来获取实例,因此必须是static的。
17      * 方法实现较为简单,因为instance已经在加载类的时候被初始化好了,所以不存在多线程并发造成的问题
18      */
19     public static SingleTon getInstance(){
20         return instance;
21     }
22 }

饿汉式的优点是:只有第一次用到的时候才进行初始化,故在还没用到的时候,会省内存; 缺点是:需要用synchronized关键字进行锁的控制,性能会差一些,代码写起来麻烦些。饿汉式的实现代码如下:

 1 /* 饿汉式单例
 2  * @author James Chong
 3  */
 4 public class SingleTon {
 5
 6     private static volatile SingleTon instance;//volitile稍后会解释
 7     public String url;
 8
 9     private SingleTon(){
10         super();
11         url = "http://www.laomiaopu.com";
12     }
13
14     public static SingleTon getInstance(){
15         if(null == instance){
16             synchronized(SingleTon.class){//稍后会解释为什么synchronized要这么用,并且需要判断两次null == instance
17                 if(null == instance){
18                     instance = new SingleTon();
19                 }
20             }
21         }
22         return instance;
23     }
24
25     public static synchronized void f1(){
26         System.out.println("吉姆斯是个大帅哥!");
27         System.out.println("http://www.laomiaopu.com");
28     }
29 }

1. 一般用synchronized修饰第14行的getInstance()方法就可以了,干嘛要把synchronized块写到函数体里呢?

原因如下:假设SingleTon这个类还有个synchronized的static函数f1(),那么,一旦调用f1()正在进行的时候,SingleTon的class对象会被锁住,故而getInstance()方法就要等待了。同理,一旦调用getInstance()正在进行的时候,f1()就无法被调用,只能等待getInstance()执行完了才能执行。所以把synchronized写到函数里面,实际上是减小了锁的粒度。这样当instance已经被实例化了的时候,getInstance()这个函数是不会加锁的,故不影响f1()函数的调用。

2. 为啥要判断两次null == instance呢?把第15行的外层if判断去掉不行吗?

行!去掉也不会出错,但是如果判断两次性能更好。因为如果只判断一次的话,每次执行getInstance()函数都要对synchronized块进行加锁;而如果进行两次判断的话,只有第一次调用的时候(即instance==null)的时候会对synchronized块进行加锁,其他时候(如果发现instance!=null)是不需要加锁的。

3.把第17行的内存if判断去掉不行吗?

  不行!很有可能当外层判断的时候,instance确实是null;但是到了synchronized块中,执行第18行的instance = new SingleTon();的时候,instance已经被其他的线程给实例化了。

4. volatile是否有必要?

如果只判断一次null==instance的话,就没必要了。但如果像本博客代码里写的那样,判断了两次null==instance,那么volatile是必要的。

为什么呢?举个反例,假设线程thread1走到了第15行的if判断发现instance==null成立,于是都进入了外部的if体。这时候thread1先获取了synchronized块的锁,于是thread1线程会执行第18行的instance = new SingleTon();这句代码,问题就出在这里,这条语句它不是原子性执行的。在Java里,实例化一个对象的过程简单地讲,可以分为两步1)先为instance对象分配一块内存,2)在这块内存里为instance对象里的成员变量赋值(比如第11行里为url赋值)。假设当thread1执行完第1)步而还没有执行第2)步的时候,另外一个线程thread2走到了第15行,这时候instance已经不是null了,于是thread2直接返回了这个instance对象。有什么问题呢?instance对象的初始化(变量赋值等操作)还没执行完呢!thread2里直接得到了一个没有初始化完全的对象,就有可能导致很严重的问题了。

那么volatile关键字有啥作用呢?当用volatile修饰了instance变量之后,对instance的写操作”先行发生“于对它的读操作。(这是Java虚拟机里的先行发生原则,不明白的网友可以留言或是看看Java虚拟机的相关资料。)这样就保证了,thread1中的instance变量被完全初始化之后,thread2才能读取它,当没有完成初始化时,thread2只能等会儿啦。

时间: 2024-08-19 11:11:04

Java懒汉式单例模式详解的相关文章

9种Java单例模式详解(推荐)

单例模式的特点 一个类只允许产生一个实例化对象. 单例类构造方法私有化,不允许外部创建对象. 单例类向外提供静态方法,调用方法返回内部创建的实例化对象.  懒汉式(线程不安全) 其主要表现在单例类在外部需要创建实例化对象时再进行实例化,进而达到Lazy Loading 的效果. 通过静态方法 getSingleton() 和private 权限构造方法为创建一个实例化对象提供唯一的途径. 不足:未考虑到多线程的情况下可能会存在多个访问者同时访问,发生构造出多个对象的问题,所以在多线程下不可用这种

java移位运算符详解[转]

java移位运算符不外乎就这三种:<<(左移).>>(带符号右移)和>>>(无符号右移). 1. 左移运算符 左移运算符<<使指定值的所有位都左移规定的次数. 1)它的通用格式如下所示: value << num num 指定要移位值value 移动的位数. 左移的规则只记住一点:丢弃最高位,0补最低位 如果移动的位数超过了该类型的最大位数,那么编译器会对移动的位数取模.如对int型移动33位,实际上只移动了332=1位. 2)运算规则 按

java 代理模式详解

java 动态代理(JDK和cglib) 设计模式这东东每次看到就明白可过段时间又不能很流利的说出来,今天就用详细的比喻和实例来加深自己的理解(小弟水平不高有不对的地方希望大家能指出来). (1)代理这个词生活中有很多比如在街边卖手机卡.充公交地铁卡的小商店他们都起了代理的作用,java中的代理跟这些小店商的作用是一样的.再比如我想在淘宝上开个服装店但又没有货源怎么办,这时候我就要跟淘宝上某一卖家联系做他的代理.我跟我的商家都要卖衣服(就好比我们都继承了卖衣服的接口sellClothesInte

设计模式 - 迭代器模式(iterator pattern) Java 迭代器(Iterator) 详解

迭代器模式(iterator pattern) Java 迭代器(Iterator) 详解 本文地址: http://blog.csdn.net/caroline_wendy 参考迭代器模式(iterator pattern): http://blog.csdn.net/caroline_wendy/article/details/35254643 Java的标准库(util)中包含迭代器接口(iterator interface), import java.util.Iterator; 继承(

java内存泄露详解

很多人有疑问,java有很好的垃圾回收机制,怎么会有内存泄露?其实是有的,那么何为内存泄露?在Java中所谓内存泄露就是指在程序运行的过程中产生了一些对象,当不需要这些对象时,他们却没有被垃圾回收掉,而且程序运行中很难发现这个对象,它始终占据着内存却没有发挥作用. 我举这样一个例子,在现实开发中我们需要自定义一个先进后出的栈集合,代码如下: 这个代码看起来和运行起来都没问题,但是,这里有个很隐晦的问题,就是在pop()方法里面,我们首先找到集合最后一个元素的下标,然后按照下标从集合中取出,但是这

java移位运算符详解

java移位运算符不外乎就这三种:<<(左移).>>(带符号右移)和>>>(无符号右移).1.左移运算符左移运算符<<使指定值的所有位都左移规定的次数.1)它的通用格式如下所示:value << numnum 指定要移位值value 移动的位数.左移的规则只记住一点:丢弃最高位,0补最低位如果移动的位数超过了该类型的最大位数,那么编译器会对移动的位数取模.如对int型移动33位,实际上只移动了332=1位. 2)运算规则按二进制形式把所有的

Java关键字synchronized详解

Java关键字synchronized详解 博客分类: Java综合 Java多线程thread互联网制造 synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程A每次运行到这个方法时,都要检查有没有其它正在用这个方法的线程B(或者C D等),有的话要等正在使用这个方法的线程B(或者C D)运行完这个方法后再运行此线程A,没有的话,直接运行 它包括两种用法:synchronized 方法和 synchronized 块. 1. synchronized 方法: 通过在方法声明中

转:Java HashMap实现详解

Java HashMap实现详解 转:http://beyond99.blog.51cto.com/1469451/429789 1.    HashMap概述: HashMap是基于哈希表的Map接口的非同步实现.此实现提供所有可选的映射操作,并允许使用null值和null键.此类不保证映射的顺序,特别是它不保证该顺序恒久不变. 2.    HashMap的数据结构: 在java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造

Java网络编程详解

Java网络编程详解 http://blog.csdn.net/he90227/article/details/39184247 Java网络编程详解