zoukankan      html  css  js  c++  java
  • 【设计模式学习笔记】之 单例模式

    1.作用:

    产生唯一实例,拒绝客户端程序员使用new关键字获取实例,即一个类只有一个实例。比如:有一个类用于读取配置文件生成一个Properties对象,只需要一个对象即可。如果每次用到就读取一次新建一个Properties实例,这样就会造成资源浪费,以及多线程的安全问题。单例模式区分懒汉式、饿汉式。

    2.观点:

    严格来说,单例模式并不应该算得上设计模式,纠结线程安全问题可以,但是纠结茴香豆的八种写法这就不好了吧,代码应该先实现,然后在保证效果的前提下再去提升效率,过度设计并不适合所有场景。

    3.懒汉、饿汉共同点:

    都私有化构造方法,有一个静态方法返回生成的唯一实例。

    4.饿汉式【推荐】:

    说明:需要单例的类,在classloader load进内存的时候,直接静态初始化自己的类本身,产生一个唯一对象,使用一个静态方法返回这个实例。

     1 package com.mi.singleton;
     2 
     3 /**
     4  * 单例模式:饿汉
     5  * 
     6  * 优点:单例模式中最简单,线程安全
     7  */
     8 public class Singleton1 {
     9 
    10     // 私有化构造方法,防止客户端程序员new对象
    11     private Singleton1() {
    12     }
    13 
    14     // 静态初始化,产生唯一实例
    15     private static Singleton1 singleton = new Singleton1();
    16 
    17     // 返回实例方法
    18     public static Singleton1 getInstance() {
    19         return singleton;
    20     }
    21 
    22 }

    多线程下安全,那么就肯定是安全的了,所以特意写了个多线程测试类(线程类我也防止这个类代码里了)

     1 package com.mi.singleton;
     2 
     3 public class Test {
     4 
     5     public static void main(String[] args) {
     6         
     7         ThreadTest[] tt = new ThreadTest[10];  
     8         for(int i = 0 ; i < tt.length ; i++){  
     9             tt[i] = new ThreadTest();
    10         }  
    11           
    12         for (int j = 0; j < tt.length; j++) {  
    13             (tt[j]).start();  
    14         }  
    15     }
    16 }
    17 
    18 class ThreadTest extends Thread{
    19 
    20     @Override
    21     public void run() {
    22         //打印实例返回实例的hashcode
    23         System.out.println(Singleton1.getInstance().hashCode());
    24     }
    25 
    26 }

    输出:

    644512395
    644512395
    644512395
    644512395
    644512395
    644512395
    644512395
    644512395
    644512395
    644512395

    由此可证,饿汉式单例模式只产生了一个实例且线程安全、简单易懂的。

    懒汉式:

    懒汉式的实现分为线程安全和不安全的情况,所以有几种实现方式,以下分别写出并测试:

    1)普通【不推荐:thread unsafe】:

     1 package com.mi.singleton;
     2 
     3 /**
     4  * 单例模式:懒汉式
     5  * 
     6  * 原始版懒汉式
     7  * 优点:所谓的省内存,没有在初始化这个类的同时初始化这个成员
     8  * 缺点:多线程不安全,多个线程第一次访问这个方法,当时singleton没有初始化,那么都会去new一个对象
     9  *              这样就没有满足单例
    10  */
    11 public class Singleton2 {
    12 
    13     private Singleton2(){}
    14     private volatile static Singleton2 singleton; //volatile关键字刷新缓存
    15     public static Singleton2 getInstance(){
    16         if(singleton == null){
    17             try {
    18                 //当前线程睡眠,测试结果更明显
    19                 Thread.sleep(300);
    20             } catch (InterruptedException e) {
    21                 e.printStackTrace();
    22             }
    23             singleton = new Singleton2();
    24         }
    25         return singleton;
    26     }
    27 }

    修改ThreadTest中run方法中的Singleton1改为Singleton2,测试输出:

    2049977720
    1798105197
    644512395
    294592865
    516266445
    257688302
    1483688470
    1906199352
    860588932
    1388138972

    2)同步方法实现【不推荐:speed slow】

     1 package com.mi.singleton;
     2 
     3 /**
     4  * 单例模式:懒汉式
     5  * 
     6  * 同步方法方式解决多线程访问问题
     7  * 优点:线程安全
     8  * 缺点:线程虽然安全了,但是多线程情况下,同步方法会导致阻塞,影响其他线程的速度
     9  */
    10 public class Singleton3 {
    11 
    12     private Singleton3(){}
    13     private static Singleton3 singleton;
    14     public static synchronized Singleton3 getInstance(){
    15         if(singleton == null){
    16             try {
    17                 //当前线程睡眠,测试结果更明显
    18                 Thread.sleep(300);
    19             } catch (InterruptedException e) {
    20                 e.printStackTrace();
    21             }
    22             singleton = new Singleton3();
    23         }
    24         return singleton;
    25     }
    26     
    27 }

    修改ThreadTest中run方法中的Singleton2改为Singleton3,测试输出:

    2049977720
    2049977720
    2049977720
    2049977720
    2049977720
    2049977720
    2049977720
    2049977720
    2049977720
    2049977720

    3)三检查方式【推荐:lazy load & thread safe】

     1 package com.mi.singleton;
     2 
     3 /**
     4  * 单例模式:懒汉式
     5  * 
     6  * 使用三检查方式
     7  * 优点:解决了多线程访问问题,同时也解决了效率问题
     8  * 缺点:代码可读性差
     9  */
    10 public class Singleton4 {
    11 
    12     private Singleton4 (){}
    13     private static Singleton4 singleton;
    14     public static Singleton4 getInstance(){
    15         if(singleton == null){ //判断,只有该对象还没有初始化的时候,才会进入
    16             //同步锁,保证只有一个线程进入
    17             synchronized(Singleton4.class){
    18                 //确保当前对象还没有被初始化,才去初始化
    19                 if(singleton == null){
    20                     try {
    21                         //当前线程睡眠,测试结果更明显
    22                         Thread.sleep(300);
    23                     } catch (InterruptedException e) {
    24                         e.printStackTrace();
    25                     }
    26                     singleton = new Singleton4();
    27                 }
    28             }
    29         }
    30         return singleton;
    31     }
    32 }

    修改ThreadTest中run方法中的Singleton3改为Singleton4,测试输出:

    644512395
    644512395
    644512395
    644512395
    644512395
    644512395
    644512395
    644512395
    644512395
    644512395

    4)静态内部类方式【不推荐:代码可读性差】

     1 package com.mi.singleton;
     2 
     3 /**
     4  * 单例模式:懒汉式
     5  * 
     6  * 静态内部类的实现 优点:解决了多线程的安全问题 缺点:代码可读性不好
     7  * 评价:该方法使用了静态内部类的初始化时机来初始化内部类中的对象,
     8  *               感觉相当于饿汉式,而且 代码看起来简单,其实更麻烦了,可读性差,比较投机取巧
     9  */
    10 public class Singleton5 {
    11 
    12     private Singleton5() {
    13     }
    14 
    15     private static class InnerClass {
    16         private static Singleton5 singleton = new Singleton5();
    17     }
    18 
    19     public static Singleton5 getInstance() {
    20         return InnerClass.singleton;
    21     }
    22 
    23 }

    修改ThreadTest中run方法中的Singleton4改为Singleton5,测试输出:

    1483688470
    1483688470
    1483688470
    1483688470
    1483688470
    1483688470
    1483688470
    1483688470
    1483688470
    1483688470

    总结:

    • 懒汉式、饿汉式区别:懒汉式是有需要的时候初始化对象(个人认为内部类方式应该算在饿汉式中),而饿汉式是加载类的同时初始化对象

    • 纯粹的懒汉式会导致并发访问的时候,出现初始化多次的问题,针对这个问题的解决方案有以下几种:

    1. 三检查:在获取对象方法中先判断是否为空,在if判断内部加入同步块,在同步块中继续判断是否为空,为空则初始化对象返回,缺点:麻烦,代码可读性差
    2. 同步获取实例方法:将获取实例方法同步,虽然解决了并发访问的问题,但效率偏低,每一次调用都要同步阻塞等待锁释放
    3. 静态内部类初始化:静态内部类会在类加载的顺序初始化该类中的成员变量(该实例),这样一来,的确可以获取到实例并避免了初始化多个对象的问题,基本等同饿汉式,缺点是代码可读性最差
  • 相关阅读:
    异常处理与调试2
    异常处理与调试
    异常处理与调试
    身份证校验程序(下)- 零基础入门学习Delphi49
    身份证校验程序(下)- 零基础入门学习Delphi49
    身份证校验程序(上)- 零基础入门学习Delphi48
    身份证校验程序(上)- 零基础入门学习Delphi48
    python -- 面向对象三大特性
    python -- 面向对象初识
    python成长之路八 -- 内置函数
  • 原文地址:https://www.cnblogs.com/hellxz/p/8425590.html
Copyright © 2011-2022 走看看