Java单例模式的知识点详解

懒汉模式与饿汉模式

懒汉模式就是懒加载,用到的时候去加载,存在线程安全问题,需要手动地加锁控制。它的优点是类加载的速度比较快,按需加载,节省资源。

饿汉模式就是在类加载的时候会创建出实例。它天生就不存在线程安全问题。但是类加载的速度会变慢且耗费资源。

懒汉模式-单重检查

示例代码如下:

public class LazySingleton {

  private static LazySingleton singletoninstance = null;
  private Object data = new Object();

//私有化构造方法
  private LazySingleton(){

  }
//加锁访问
  public static synchronized LazySingleton getInstance(){

    if(singletoninstance == null){
      singletoninstance = new LazySingleton();
    }
    return singletoninstance;
  }

  public Object getData() {
    return data;
  }

  public void setData(Object data) {
    this.data = data;
  }
}

测试代码如下:

public class TestThread extends Thread {

  @Override
  public void run() {

    LazySingleton instance = LazySingleton.getInstance();
    System.out.println(instance.getData());
  }
}

public static void main(String[] args) {

    for(int i =0;i < 10;i++){
      TestThread t = new TestThread();
      t.start();
    }
  }
}

运行结果如下:

java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64
java.lang.Object@306d3b64

打印出同一个object对象,表明是从同一个LazySingleton对象中获取的数据。

但是上述代码存在一个显著的问题:多个线程同时访问getInstance()方法都是排队式的,即使该instance已经被创建的情况下。然而,如果该instance已经被创建,是可以支持并发访问的。需要对锁的控制细粒度化。

懒汉模式-双重检查

public class LazySingleton {
//声明为volatile变量
  private static volatile LazySingleton singletoninstance = null;
  private Object data = new Object();

  private LazySingleton(){

  }

  public static synchronized LazySingleton getInstance(){

    if(singletoninstance == null){
      synchronized (LazySingleton.class) {
        //这个第二重的的检查是必要的
        if(singletoninstance == null)
          singletoninstance = new LazySingleton();
      }
    }
    return singletoninstance;
  }

  public Object getData() {
    return data;
  }

  public void setData(Object data) {
    this.data = data;
  }
}

第二重检查是为了防止:

线程A发现instance未被创建,于是申请锁,进入临界区创建instance;于此同时另一个线程也发现instance未被创建,于是也要申请锁去创建instance,问题就这样发生了。而且,这个instance变量要被声明为volatile,也就是其中一个线程对它就行修改之后(也就是实例化),这一修改立马对其他线程可见,避免了无谓的等待。

检查代码同上,运行结果同上。

饿汉模式

public class HungerSingleton {

  private static final HungerSingleton singletoninstance = new HungerSingleton();
  private Object data = new Object();

  private HungerSingleton(){

  }

  public static HungerSingleton getInstance(){

    return singletoninstance;
  }

  public Object getData() {
    return data;
  }

  public void setData(Object data) {
    this.data = data;
  }

}

在加载该类的时候就立马去实例化instance,不存在线程安全问题(由jvm保证线程安全问题),但是存在资源浪费、加载速度慢的问题。

检查代码同上,运行结果同上。

Holder模式

就是利用一个静态内部类来实现instance的实例化。这里利用了静态内部类的一个特性:该内部类的实例与外部类的实例 没有绑定关系,而且只有被调用到才会装载,从而实现了延迟加载

public class HolderSingleton {

  private Object data = new Object();

  private HolderSingleton(){

  }

  private static class InnerClass{

    private static HolderSingleton singletoninstance = new HolderSingleton();
  }

  public static HolderSingleton getInstance(){

    return InnerClass.singletoninstance;
  }

  public Object getData() {
    return data;
  }

  public void setData(Object data) {
    this.data = data;
  }
}

测试代码同上,运行结果同上。

在加载InnerClass的时候才会去实例化这个instance,实现了延迟加载,并且同饿汉模式一样,由jvm保证线程安全。这种方法值得推荐。

应用场景:

在整个系统中,只允许共用一个实例的类适合用单例模式来实现,比如:

网站的计数器,只允许存在一个计数器实例;

线程池,只允许存在一个线程池对象;

连接池,只允许存在一个连接池对象;

知识点扩充:

1.为什么要使用单例模式?

在我们日常的工作中,很多对象通常占用非常重要的系统资源,比如:IO处理,数据库操作等,那我们必须要限制这些对象只有且始终使用一个公用的实例,即单例。

2.单例模式的实现方式

构造函数私有化,防止其他类生成唯一公用实例外的实例。且单例类应该被定义为final,也就是说单例类不能被继承,因为如果允许继承那子类就都可以创建实例,违背了类唯一实例的初衷。

类中一个静态变量来保存单实例的引用。

一个共有的静态方法来获取单实例的引用。
3.单例模式的UML类图

4.单例模式的经典实现方式

  • 饿汉式:一开始就创建好实例,每次调用直接返回,经典的“拿空间换时间”。
  • 懒汉式:延迟加载,第一次调用的时候才加载,然后返回,以后的每次的调用就直接返回。经典“拿时间换空间”,多线程环境下要注意解决线程安全的问题。
  • 登记式:对一组单例模式进行的维护,主要是在数量上的扩展,通过线程安全的map把单例存进去,这样在调用时,先判断该单例是否已经创建,是的话直接返回,不是的话创建一个登记到map中,再返回。

以上就是本次我们小编结合多篇整理的相关内容,希望能够帮助到大家。

(0)

相关推荐

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

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

  • Java版的7种单例模式写法示例

    前言 今天看到某一篇文章的一句话 单例DCL 前面加 V .就这句话让我把 单例模式 又仔细看了一遍. Java 中的 单例模式 是我们一直且经常使用的设计模式之一,大家都很熟悉,所以这篇文章仅仅做我自己记忆. 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 单例模式 涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建.这个类提供了一种访问其唯一的对象的方式,可以直接访问,

  • Java单例模式和多例模式实例分析

    本文实例讲述了Java单例模式和多例模式.分享给大家供大家参考,具体如下: 一 单例模式 1 代码 class Boss { private static Boss instance;// 静态成员变量,用来保存唯一创建的对象实例 private Boss () { // 利用私有化构造方法,阻止外部创建对象 } public static Boss findBoss() //检查并确保只有一个实例 { if (instance == null) { System.out.println("当前

  • 为何Java单例模式我只推荐两种

    双重检查模式 public class Singleton { private volatile static Singleton singleton; //1:volatile修饰 private Singleton (){} public static Singleton getSingleton() { if (singleton == null) { //2:减少不要同步,优化性能 synchronized (Singleton.class) { // 3:同步,线程安全 if (sin

  • Java设计模式单例模式(Singleton)用法解析

    这篇文章主要介绍了Java设计模式单例模式(Singleton)用法详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 单例模式的应用场景: 单例模式(Singleton Pattern)是指确保一个类在任何情况下都绝对只有一个实例.并提供一个全局反访问点.单例模式是创建型模式.单例模式在生活中应用也很广泛,比如公司CEO只有一个,部门经理只有一个等.JAVA中ServletCOntext,ServetContextCOnfig等,还有spri

  • Java单例模式的知识点详解

    懒汉模式与饿汉模式 懒汉模式就是懒加载,用到的时候去加载,存在线程安全问题,需要手动地加锁控制.它的优点是类加载的速度比较快,按需加载,节省资源. 饿汉模式就是在类加载的时候会创建出实例.它天生就不存在线程安全问题.但是类加载的速度会变慢且耗费资源. 懒汉模式-单重检查 示例代码如下: public class LazySingleton { private static LazySingleton singletoninstance = null; private Object data =

  • java 单例模式的实例详解

    java 单例模式的实例详解 概念: java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种. 单例模式有一下特点: 1.单例类只能有一个实例. 2.单例类必须自己自己创建自己的唯一实例. 3.单例类必须给所有其他对象提供这一实例. 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例.在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.这些应用都或多或少具有资源管理器的功能.每台计算机可以有若干个打

  • java super关键字知识点详解

    在对一些父类的调用上,我们需要借助java中的关键字使用,这就是super关键字,可以归纳为三种方法:作用于类.方法名和构造函数.可以把它看做是使用父类的一个工具,相信大家在之前类的使用中有所接触.下面我们就super的说明.三种用法.注意事项进行说明,然后在实例中体会用法. 1.说明 super相当于是指代当前的对象的父类,调用父类的属性.方法和构造方法 2.用法 (1)super.变量/对象名: 使用这种方法可以直接访问父类中的变量或对象,进行修改赋值等操作 (2)super.方法名():

  • java伪泛型知识点详解

    说明 1.Java中的泛型是伪泛型.这种泛型实现方法称为类型擦除 ,基于这种方法实现的泛型称为伪泛型. 2.由于Java的泛型只在编译阶段发挥作用,因此在写代码时,起到了检查的作用,当代码运行时,它的内部并没有泛型. 实例 List<String> l1 = new ArrayList<String>(); List<Integer> l2 = new ArrayList<Integer>(); System.out.println(l1.getClass(

  • Java字符串编码知识点详解介绍

    在 Java 中,当我们处理String时,有时需要将字符串编码为特定字符集.编码是一种将数据从一种格式转换为另一种格式的方法.字符串对象使用 UTF-16 编码.UTF-16 的问题在于它不能被修改.只有一种方法可以用来获得不同的编码,即 byte[] 数组.如果我们得到意外的数据,编码的方式是不合适的.在本节中,我们将学习如何在Java中对字符串进行编码. 在继续本节之前,我们必须了解字符编码.让我们快速浏览一下.让我们了解为什么我们需要对字符串进行编码. 字符编码是一种将文本数据转换为二进

  • Java位运算知识点详解

    在日常的Java开发中,位运算使用的不多,使用的更多的是算数运算(+.-.*./.%).关系运算(<.>.<=.>=.==.!=)和逻辑运算(&&.||.!),所以相对来说对位运算不是那么熟悉,本文将以Java的位运算来详细介绍下位运算及其应用. 1. 位运算起源 位运算起源于C语言的低级操作,Java的设计初衷是嵌入到电视机顶盒内,所以这种低级操作方式被保留下来.所谓的低级操作,是因为位运算的操作对象是二进制位,但是这种低级操作对计算机而言是非常简单直接,友好高效

  • java二进制运算基础知识点详解

    一.二进制位运算 1. 按位与(&) 位运算实质是将参与运算的数字转换为二进制,而后逐位对应进行运算. 按位与运算为:两位全为1,结果为1,即1&1=1,1&0=0,0&1=0,0&0=0. 例如51 & 5 -> 00110011 & 00000101 = 00000001 -> 51 & 5 = 1 特殊用法: (1)与0相与可清零. (2)与1相与可保留原值,可从一个数中取某些位.例如需要取10101110中的低四位,101

  • 基于java集合中的一些易混淆的知识点(详解)

    (一) collection和collections 这两者均位于java.util包下,不同的是: collection是一个集合接口,有ListSet等常见的子接口,是集合框架图的第一个节点,,提供了对集合对象进行基本操作的一系列方法. 常见的方法有: boolean add(E e) 往容器中添加元素:int size() 返回collection的元素数:boolean isEmpty() 判断此容器是否为空: boolean contains(Object o) 如果此collecti

  • java.lang.Runtime.exec() Payload知识点详解

    有时,通过Runtime.getRuntime().exec()执行命令的有效负载有时会失败.使用Web Shell,反序列化利用或通过其他媒介时,可能会发生这种情况. 有时这是因为重定向和管道字符的使用在启动过程的上下文中没有意义.例如,在shell中执行ls> dir_listing会将当前目录的列表输出到名为dir_listing的文件中.但是在exec()函数的上下文中,该命令将被解释为获取>和dir_listing目录的列表. 有时,StringTokenizer类会破坏其中包含空格

  • Java四种权限修饰符知识点详解

    Java中有四种权限修饰符 public protected (default) private 同一个类 yes yes yes yes 同一个包 yes yes yes no 不同包子类 yes yes no no 不同包非子类 yes no no no Warning:(default)并不是关键字"default",而是根本不写 知识点补充: Java语言4种访问权限修饰符 Java语言4种访问权限修饰符,但是仅有3个关键字,因为不写访问权限,在Java中被称为默认权限,或同包

随机推荐