懒汉单例模式 java_Java懒汉式单例模式详解

标签:

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

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

1 /**

2 * 懒汉式单例3 *@authorJames Chong4 *5 */

6 public classSingleTon {7

8 //加载类的时候会初始化static的instance,从这以后,这个static的instance对象便一直占着这段内存,永远不会被回收掉。

9 private static SingleTon instance = newSingleTon();10

11 private SingleTon(){//将构造函数private掉,避免直接new SingleTon()

12 super();13 }14

15 /**

16 * 因为是单例,所以只能通过static方法来获取实例,因此必须是static的。17 * 方法实现较为简单,因为instance已经在加载类的时候被初始化好了,所以不存在多线程并发造成的问题18 */

19 public staticSingleTon getInstance(){20 returninstance;21 }22 }

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

1 /*饿汉式单例2 * @author James Chong3 */

4 public classSingleTon {5

6 private static volatile SingleTon instance;//volitile稍后会解释

7 publicString url;8

9 privateSingleTon(){10 super();11 url = "http://www.laomiaopu.com";12 }13

14 public staticSingleTon getInstance(){15 if(null ==instance){16 synchronized(SingleTon.class){//稍后会解释为什么synchronized要这么用,并且需要判断两次null == instance

17 if(null ==instance){18 instance = newSingleTon();19 }20 }21 }22 returninstance;23 }24

25 public static synchronized voidf1(){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只能等会儿啦。

标签:

### Java懒汉单例模式的实现与使用 #### 一、懒汉单例模式的核心概念 懒汉单例模式是一种确保类只有一个实例的设计模,其实现特点是只有当对象首次被调用时才会创建实例。这种延迟加载的方有助于减少必要的资源消耗[^1]。 #### 二、懒汉单例模式的优点与局限性 - **优点**: - 只有在需要时才创建实例,减少了内存占用。 - 涉及复杂的线程同步逻辑,在单线程环境下性能较高[^3]。 - **局限性**: - 如果多个线程同时尝试获取实例,则可能导致多次实例化问题,因此需要引入 `synchronized` 关键字来保证线程安全性[^2]。 #### 三、懒汉单例模式的标准实现 以下是懒汉单例模式的一个典型实现: ```java public class Singleton { // 1. 定义私有的静态变量,初始值为 null private static Singleton instance = null; // 2. 构造函数设为私有,防止外部通过 new 创建实例 private Singleton() { System.out.println("Singleton instance created!"); } // 3. 提供公共的静态方法,用于返回唯一的实例 public static synchronized Singleton getInstance() { if (instance == null) { // 检查是否已存在实例 instance = new Singleton(); // 若未创建则初始化 } return instance; } // 4. 示例方法 public void showMessage() { System.out.println("This is a lazy initialization singleton."); } } ``` 在此代码中: - 使用了 `private static` 类型的成员变量存储唯一实例。 - 将构造函数设置为私有,阻止外界直接实例化该类。 - 在 `getInstance()` 方法中加入双重检查机制(可选),进一步提升效率[^4]。 #### 四、懒汉单例的实际应用场景 懒汉单例常应用于需要全局共享状态且需要频繁初始化的对象,例如日志管理系统。下面展示了一个基于懒汉单例的日志管理器示例: ```java public class LogManager { // 1. 静态变量,保存唯一实例,但立即初始化 private static LogManager instance = null; // 2. 私有构造方法,防止外部实例化 private LogManager() { System.out.println("LogManager initialized!"); } // 3. 提供一个静态方法访问唯一实例 public static synchronized LogManager getInstance() { if (instance == null) { instance = new LogManager(); } return instance; } // 4. 示例方法,用于记录日志 public void log(String message) { System.out.println("Log: " + message); } } // 测试代码 class Test { public static void main(String[] args) { LogManager logger = LogManager.getInstance(); logger.log("Application started."); } } ``` 此代码展示了如何利用懒汉单例模式构建一个高效的日志管理工具[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值