多线程单例模式懒汉和饿汉,java多线程代码例子

  多线程单例模式懒汉和饿汉,java多线程代码例子

  00-1010前言:1。单体模式概述;2.单例模式的简单实现;2.1饿汉模式;2.2懒人模式;2.3单例模式的枚举实现

  

目录
本文将介绍Java多线程中几种典型情况的singleton模式。所谓单例模式,就是一个类只有一个实例对象。本文将关注在多线程环境中单一模式的简单实现。

  00-1010单例模式是一种常见的软件设计模式。它的核心结构只包含一个叫做singleton的特殊类。Singleton模式可以确保系统中只有一个应用该模式的类实例,也就是说,一个类只有一个对象实例。

  singleton模式的典型实现有两种,一种是饿汉模式,一种是懒汉模式。Hungry-Chinese模式下的Hungry其实并不是“饿”的意思,更准确地说是“急”的意思,即在使用之前已经创建了一个singleton对象,而lazy-Chinese模式是在使用之前才创建singleton对象的。

  singleton模式中的singleton类只能有一个instance对象,static修改的成员属于该类,即只有一个副本,所以我们可以使用static修改的成员变量来保存instance对象的引用。

  

前言:

  00-1010由于一个类在singleton模式下只能有一个实例对象,所以需要封装类构造方法,防止用多个实例对象创建类。但是在使用这个类的时候,需要获取这个类的实例对象,所以我们要创建一个方法getInstance来获取唯一的实例对象。

  对于这个类的实例对象,我们可以使用该类所属的成员变量来保存(即静态成员变量)。

  //单例模式-饥饿单例模式类HungrySingleton {//1。使用变量来保存这个类的唯一实例,因为singleton模式在一个程序中只能有一个实例。由于只有一个静态成员,我们可以使用静态变量保存私有静态final hungry singleton实例=new hungry singleton();//2.封装构造方法,防止类被实例化为新对象,private HungrySingleton() {} //3。获取该类的唯一实例对象,Public Hungry Singleton GetInstance(){ Return Instance;}}在多线程的情况下,对于上述Hungry-Chinese singleton模式的简单实现,只需要考虑getInstance方法是否是线程安全的。因为这个方法只返回一个语句,也就是说,读操作是线程安全的,所以getInstance方法是线程安全的。总之,饥饿的中国单例模式是线程安全的。

  00-1010懒惰模式与饥饿模式的区别在于实例对象的创建时间不同。懒惰模式需要等到第一次使用时才能创建实例对象,所以只需要修改获取对象的方法。

  不考虑多线程情况,懒汉模式实现代码如下:

  //singleton模式-懒惰模式类SlackerSingleton {//1。使用变量来保存这个类的唯一实例,因为singleton模式在一个程序中只能有一个实例。由于只有一个静态成员,我们可以使用静态变量来保存它。//Lazy singleton模式在使用时创建了对象,所以一开始就不应该将对象创建为私有静态Slacker Singleton实例;//2.封装构造方法,防止类被实例化成新对象,private SlackerSingleton() {} //3。获取类的唯一对象,如果没有,创建公共Slacker Singleton GetInstance(){ if(instance==null){ instance=new Slacker Singleton();}返回实例;}}多线程的情况下,由于getInstance方法中有两次读取(一次判断一次返回)和一次写入(修改intsance变量的值),初始化实例变量时(即instance=null),可能会有多个线程进入判断语句,这样这个类就可能被多个对象实例化,所以上面实现的lazy singleton模式是线程不安全的。

  导致线程不安全的代码段在if语句中。

  面的读操作和instance的修改操作,所以我们需要对这段代码进行加锁,然后就得到了线程安全的懒汉模式:

  

//多线程情况下饿汉模式获取对象时只读不修改,所以是线程安全的//多线程情况下懒汉模式获取对象时存在两次读操作,分别为判断instance是否为null和返回instance,除了读操作还存在修改操作,即新建对象并使instance指向该对象//懒汉模式对象还未初始化的时候,可能会存在多个线程进入判断语句,会导致实例出多个对象,因此懒汉单例模式是线程不安全的。//线程安全单例模式 - 懒汉模式class SafeSlackerSingleton { //1.使用一个变量来保存该类唯一的实例,因为单例模式在一个程序中只能拥有一个实例,由于static成员只有一份,我们可以使用static变量来保存 //懒汉单例模式是在使用的时候创建对象,因此初始时对象不应该被创建 private static SafeSlackerSingleton instance; //2.封装构造方法,防止该类被实例出新的对象 private SafeSlackerSingleton() {} //3.获取该类的唯一对象,如果没有就创建 public SafeSlackerSingleton getInstance() { synchronized (SafeSlackerSingleton.class) { if (instance == null) { instance = new SafeSlackerSingleton(); } } return instance; }}
但是!上述线程安全问题只出现在instance没有初始化的时候,如果instance已经初始化了,那个判断语句就是个摆设,就和饿汉模式一样,就是线程安全的了,如果按照上面的代码处理线程安全问题,不论instance是否已经初始化,都要进行加锁,因此会使锁竞争加剧,消耗没有必要消耗的资源,所以在加锁前需要先判断一下instance是否已经初始化,如果为初始化就进行加锁。

  按照上述方案得到以下关于获取对象的方法代码:

  

 public SafeSlackerSingletonPlus getInstance() { //判断instance是否初始化,如果已经初始化了,那么该方法只有两个读操作,本身就是线程安全的,不需要加锁了,这样能减少锁竞争,提高效率 if (instance == null) { synchronized (SafeSlackerSingletonPlus.class) { if (instance == null) { instance = new SafeSlackerSingletonPlus(); } } } return instance; }
到这里线程安全的问题是解决了,但是别忘了编译器它是不信任你的,它会对你写的代码进行优化! 上面所写的代码需要判断instance==null,而多线程情况下,很可能频繁进行判断,这时候线程不会去读内存中的数据,而会直接去寄存器读数据,这时候instance值变化时,线程完全感知不到!造成内存可见性问题,为了解决该问题需要使用关键字volatile修饰instance变量,防止编译器优化,从而保证内存可见性。

  

//线程安全优化单例模式 - 懒汉模式class SafeSlackerSingletonPlus { //1.使用一个变量来保存该类唯一的实例,因为单例模式在一个程序中只能拥有一个实例,由于static成员只有一份,我们可以使用static变量来保存 //懒汉单例模式是在使用的时候创建对象,因此初始时对象不应该被创建 private static volatile SafeSlackerSingletonPlus instance; //2.封装构造方法,防止该类被实例出新的对象 private SafeSlackerSingletonPlus() {} //3.获取该类的唯一对象,如果没有就创建 public SafeSlackerSingletonPlus getInstance() { //判断instance是否初始化,如果已经初始化了,那么该方法只有两个读操作,本身就是线程安全的,不需要加锁了,这样能减少锁竞争,提高效率 //如果线程很多,频繁进行外层或内层if判断,可能会引发内层可见性问题,因此要给instan变量加上volatile if (instance == null) { synchronized (SafeSlackerSingletonPlus.class) { if (instance == null) { instance = new SafeSlackerSingletonPlus(); } } } return instance; }}

  

2.3枚举实现单例模式

除了使用饿汉和懒汉模式还可以使用枚举的方式实现,在《Effective Java》书中有这样一句话:单元素的枚举类型已经成为实现Singleton的最佳方法。 因为枚举就是一个天然的单例,并且枚举类型通过反射都无法获取封装的私有变量,非常安全。

  

//单元素的枚举类型已经成为实现Singleton的最佳方法enum EnumSingleton { INSTANCE; public void doSomething() { System.out.println("完成一些任务!"); }}
使用方式:

  

public class Singleton { public static void main(String[] args) { EnumSingleton.INSTANCE.doSomething(); }}
运行结果:

  

  好了,有关多线程单例模式问题就讨论到这里了,你学会了吗?

  到此这篇关于Java多线程案例之单例模式懒汉+饿汉+枚举的文章就介绍到这了,更多相关Java单例模式内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: