• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

无信不立

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

(4)单例模式

一:单例模式的优点

          --->单例类只能有一个实例

             --->单例类必须自己创建自己的唯一实例。

             --->单例类必须给所有其他对象提供这一实例。

二:单例模式分类

  --->饿汉模式

        --->懒汉模式


三:单例模式应用场景

        --->

四:单例模式的角色

        --->
五:单例模式的代码示例

饿汉模式:

 1 package com.yeepay.sxf.interfaces.impl;
 2 /**
 3  * 饿汉单例模式
 4  * 是典型的空间换时间,当类装载的时候就会创建类的实例,不管你用不用,先创建出来,然后每次调用的时候,就不需要再判断,节省了运行时间。
 5  * @author sxf
 6  *
 7  */
 8 public class SingObj {
 9     /**
10      * 持有自己的引用
11      */
12     private static SingObj singObj=new SingObj();
13     /**
14      *似有化构造器
15      */
16     private SingObj(){
17         
18     }
19     /**
20      * 提供静态方法返回自身对象
21      * @return
22      */
23     public static SingObj getSingObj(){
24         return singObj;
25     }
26 }
View Code

懒汉模式:

(1)线程安全的懒汉模式(并发速度缓慢)

 1 package com.yeepay.sxf.interfaces.impl;
 2 /**
 3  * 懒汉模式
 4  * 
 5  * (1)懒汉式是典型的时间换空间,就是每次获取实例都会进行判断,看是否需要创建实例,浪费判断的时间。当然,如果一直没有人使用的话,那就不会创建实例,则节约内存空间
 6  * (2)由于懒汉式的实现是线程安全的,这样会降低整个访问的速度,而且每次都要判断。那么有没有更好的方式实现呢?
 7  *
 8  */
 9 public class SingObj2 {
10     /**
11      * 持有自身引用
12      */
13     private static SingObj2 singObj2=null;
14     
15     /**
16      * 构造器私有化
17      */
18     private void  SingObj2(){
19         
20     }
21     
22     /**
23      * 提供静态方法返回对象.同步方法,防止出现多个对象,违反单例规则
24      * @return
25      */
26     public static synchronized SingObj2 getSingObj2(){
27         if(singObj2==null){
28             singObj2=new SingObj2();
29         }
30         return singObj2;
31     }
32 }
View Code

(2)线程安全的懒汉模式(并发速度提高)

 1 package com.yeepay.sxf.interfaces.impl;
 2 /**
 3  * 懒汉模式升级版
 4  * 
 5  * @author sxf
 6  *
 7  */
 8 public class SingObj3 {
 9     /**
10      * 持有自身对象的引用
11      */
12     private static SingObj3 singObj3=null;
13     
14     /**
15      * 构造器似有化
16      */
17     private void SingObj3(){
18         
19     }
20     /**
21      * 升级版的静态方法返回单例对象引用。只需要第一次创建时才用到同步。以后获取就不需要同步
22      * @return
23      */
24     public static SingObj3 getSingObje3(){
25         if(singObj3==null){
26             synchronized (SingObj3.class) {
27                 if(singObj3==null){
28                     singObj3=new SingObj3();
29                 }
30             }
31         }
32         
33         return singObj3;
34     }
35 
36 }
View Code

(3)内部类的懒汉模式

 1 package com.yeepay.sxf.interfaces.impl;
 2 /**
 3  * 懒汉模式的再次升级版
 4  * 使用类级内部类
 5  * 什么是类级内部类?
 6  *       (1)简单点说,类级内部类指的是,有static修饰的成员式内部类。如果没有static修饰的成员式内部类被称为对象级内部类。
 7   (2)类级内部类相当于其外部类的static成分,它的对象与外部类对象间不存在依赖关系,因此可直接创建。而对象级内部类的实例,是绑定在外部对象实例中的。
 8   (3)类级内部类中,可以定义静态的方法。在静态方法中只能够引用外部类中的静态成员方法或者成员变量。
 9       (4)类级内部类相当于其外部类的成员,只有在第一次被使用的时候才被会装载
10  * 
11  * 多线程缺省同步所的机制
12  * 大家都知道,在多线程开发中,为了解决并发问题,主要是通过使用synchronized来加互斥锁进行同步控制。但是在某些情况中,JVM已经隐含地为您执行了同步,这些情况下就不用自己再来进行同步控制了。这些情况包括:
13 
14   1.由静态初始化器(在静态字段上或static{}块中的初始化器)初始化数据时
15 
16   2.访问final字段时
17 
18   3.在创建线程之前创建对象时
19 
20   4.线程可以看见它将要处理的对象时
21  * @author sxf
22  *
23  */
24 public class SingObj4 {
25     /**
26      * 构造器似有化
27      */
28     private SingObj4(){}
29     /**
30      *    类级的内部类,也就是静态的成员式内部类,该内部类的实例与外部类的实例
31      *    没有绑定关系,而且只有被调用到时才会装载,从而实现了延迟加载。
32      *    一旦加载,不会再次加载
33      */
34     private static class SingletonHolder{
35         /**
36          * 静态初始化器,由JVM来保证线程安全
37          */
38         private static SingObj4 instance = new SingObj4();
39     }
40     /**
41      * 
42      * @return
43      */
44     public static SingObj4 getInstance(){
45         return SingletonHolder.instance;
46     }
47 }
View Code

(4)枚举的懒汉模式

 1 public enum Singleton {
 2     /**
 3      * 定义一个枚举的元素,它就代表了Singleton的一个实例。
 4      */
 5     
 6     uniqueInstance;
 7     
 8     /**
 9      * 单例可以有自己的操作
10      */
11     public void singletonOperation(){
12         //功能处理
13     }
14 }
View Code

 

 

单例,大家肯定都不陌生,这是Java中很重要的一个设计模式。稍微了解一点单例的朋友也都知道实现单例是要考虑并发问题的,一般情况下,我们都会使用synchronized来保证线程安全。

那么,如果有这样一道面试题:不使用synchronized和lock,如何实现一个线程安全的单例?你该如何回答?

C类应聘者:可以使用饿汉模式实现单例。如:

public class Singleton { 
     private static Singleton instance = new Singleton();
     private Singleton (){}
     public static Singleton getInstance() {
       return instance;
     }
}
View Code

还有部分程序员可以想到饿汉的变种:

public class Singleton {
     private Singleton instance = null;
     static {
         instance = new Singleton();
     }
     private Singleton (){}
     public static Singleton getInstance() {
         return this.instance;
     }
}
View Code

使用static来定义静态成员变量或静态代码,借助Class的类加载机制实现线程安全单例。

面试官:除了这种以外,还有其他方式吗?

B类应聘者:

除了以上两种方式,还有一种办法,就是通过静态内部类来实现,代码如下:

public class Singleton {
     private static class SingletonHolder {
         private static final Singleton INSTANCE = new Singleton();
     }
     private Singleton (){}
     public static final Singleton getInstance() {
         return SingletonHolder.INSTANCE;
     }
}
View Code

这种方式相比前面两种有所优化,就是使用了lazy-loading。Singleton类被装载了,但是instance并没有立即初始化。因为SingletonHolder类没有被主动使用,只有显示通过调用getInstance方法时,才会显示装载SingletonHolder类,从而实例化instance。

面试官:除了这种以外,还有其他方式吗?

A类应聘者:

除了以上方式,还可以使用枚举的方式,如:

public enum Singleton {
     INSTANCE;
     public void whateverMethod() {
     }
}
View Code

这种方式是Effective Java作者Josh Bloch 提倡的方式,它不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象,可谓是很坚强的壁垒。

面试官:以上几种答案,其实现原理都是利用借助了类加载的时候初始化单例。即借助了ClassLoader的线程安全机制。

所谓ClassLoader的线程安全机制,就是ClassLoader的loadClass方法在加载类的时候使用了synchronized关键字。也正是因为这样, 除非被重写,这个方法默认在整个装载过程中都是同步的,也就是保证了线程安全。

所以,以上各种方法,虽然并没有显示的使用synchronized,但是还是其底层实现原理还是用到了synchronized。

面试官:除了这种以外,还有其他方式吗?

A类应聘者:

还可以使用Java并发包中的Lock实现

面试官:本质上还是在使用锁,不使用锁的话,有办法实现线程安全的单例吗?

A+类面试者:

有的,那就是使用CAS。

CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。实现单例的方式如下:

public class Singleton {
     private static final AtomicReference<Singleton> INSTANCE = new AtomicReference<Singleton>();

     private Singleton() {}

     public static Singleton getInstance() {
         for (;;) {
             Singleton singleton = INSTANCE.get();
             if (null != singleton) {
                 return singleton;
             }

             singleton = new Singleton();
             if (INSTANCE.compareAndSet(null, singleton)) {
                 return singleton;
             }
         }
     }
}
View Code

面试官:这种方式实现的单例有啥优缺点吗?

A++类面试者:

用CAS的好处在于不需要使用传统的锁机制来保证线程安全,CAS是一种基于忙等待的算法,依赖底层硬件的实现,相对于锁它没有线程切换和阻塞的额外消耗,可以支持较大的并行度。


CAS的一个重要缺点在于如果忙等待一直执行不成功(一直在死循环中),会对CPU造成较大的执行开销。

另外,如果N个线程同时执行到singleton = new Singleton();的时候,会有大量对象创建,很可能导致内存溢出。

面试官:你被录取了!

posted on 2015-07-12 18:39  无信不立  阅读(315)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3