这篇是入职之后的第二篇了,上一篇我简单介绍了一下LOCK里面的类的方法,感兴趣的话可以去了解一下,以后坚持每周至少会更新一篇关于多线程方面的文章,希望博友们可以一起加油成长。

这篇主要的内容是单例模式在多线程环境下的设计,这篇算是比较重要的内容,我会进行文字和代码的共同说明来讲解记录

1、立即加载(饿汉模式)

说到标题,有人会说什么是立即加载呢?立即加载就是使用类的时候已经将对象创建完毕了,比如说直接new实例化对象。也就是在调用方法之前,实例已经被创建了

  

public class MyObject {
private static MyObject myObject = new MyObject();
private MyObject(){ }
public static MyObject getInstance(){
return myObject;
}
}

看这段代码,他的缺点是不能有其他实例变量。外部的使用者需要使用MyObject实例的时候,只能通过getInstance方法,另外假如没有用到这个实例的时候,他已经创建了出现,会有资源浪费的情况出现的。还有因为getInstance方法没有同步,所以有可能出现非线程安全的问题。

2、延迟加载(懒汉模式)

延迟加载就是在调用要使用的那个方法(假如MyObject方法)的时候实例才会被创建,实现方法就是在MyObject方法里面进行new实例化。

  

public class MyObject {
private static MyObject myObject;
private MyObject(){ }
public static MyObject getInstance(){
if(myObject==null){
myObject = new MyObject();
}
return myObject;
}
}

此代码虽然取得了一个对象,没毛病。单例!但是如果在多线程情况下,就会取出多个实例的情况,这个是与单例模式的初衷背道而驰的。

  

public class MyObject {
private static MyObject myObject;
private MyObject(){ }
public static MyObject getInstance(){
if(myObject==null){
try {
Thread.sleep(3000);
myObject = new MyObject();
}catch(Exception e){
e.printStackTrace();
}
}
return myObject;
}
}

可以试着自己多建立几个线程,运行一下这段代码,发现在多线程环境在建立出来了多个实例(可以打印对象的hashcode值进行比较)

那我们应该怎么去解决这个问题呢?

3、延迟加载(懒汉模式)——synchronized

  

public class MyObject {
private static MyObject myObject;
private MyObject(){ }
synchronized public static MyObject getInstance(){
if(myObject==null){
try {
Thread.sleep(3000);
myObject = new MyObject();
}catch(Exception e){
e.printStackTrace();
}
}
return myObject;
}
}

这样OK?看上去是的,是解决了得到了相同的实例,但是见到了synchronized这个东西,你不得犹豫一下么?加在了整个方法上啊,如果这个方法设计到比如说很多的过程或者运算,下一个线程想要取得对象,不是要等到程序员找到女朋友才行么。也就是要等到上一个线程释放锁之后,才可以继续进行。

有人说,我不加全部,我加部分不行么?

  

   public static MyObject getInstance(){
synchronized (MyObject.class) {
if (myObject == null) {
try {
Thread.sleep(3000);
myObject = new MyObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return myObject;
}
}

你仔细看,有啥大的变化么,并没有吧。因为效率还是一样的低低低低。每次我调用getIstance的时候是不是还要同步啊,所以太大变化啦

然后机智的我想出了这样的方法

  

public class MyObject {
private static MyObject myObject;
private MyObject(){ }
public static MyObject getInstance(){ if (myObject == null) {
try {
Thread.sleep(3000);
synchronized (MyObject.class) {
myObject = new MyObject();
}
} catch (Exception e) {
e.printStackTrace();
}
}
return myObject;
}
}

我只加载了需要创建对象的那个关键地方,看到了么,这样效率大大滴提升了。但是,重点来了,我靠,打印出来了两个不同对象,打印出来的对象hashcode值不一样了啊,不是一个对象了,因为两个线程都进入了if语句内,之后没有在进行判断,所以创建了两个对象。我单例什么呢?所以这个方法也pass

那我到底该怎么办呢?别着急,DCL双重检查所机制,不废话直接看代码

  

public class MyObject {
private static MyObject myObject;
private MyObject(){ }
public static MyObject getInstance(){ if (myObject == null) {
try {
Thread.sleep(3000);
synchronized (MyObject.class) {
if(myObject == null) {
myObject = new MyObject();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return myObject;
}
}

哇,终于有方法可以实现单例模式在多线程环境下的正常工作了,哈哈哈哈哈,但是但是那你们就打错特错了。看如下分析

从JVM的角度来说,怎么创建一个对象呢?第一是申请一块内存,调用构造方法进行初始化操作,第二是分配一个指针指向这块内存。这两个操作谁在前面,谁在后面JVM并不会管它。那么就存在这么一种情况,JVM是先开辟出一块内存,然后把指针指向这块内存,最后调用构造方法进行初始化。

线程A开始创建MyObject的实例,此时线程B调用了getInstance()方法,首先判断MyObject是否为null。假设A已经把MyObject指向了那块内存,只是还没有调用构造方法,因此B检测到MyObject不为null,于是直接把MyObject返回了——问题出现了,尽管MyObject不为null,但它并没有构造完成结束。此时,如果B在A将MyObject构造完成之前就是用了这个实例,程序就会出现错误了!(其实在private static MyObject myObject;  改为   private volatile static MyObject myObject;  就不会发生这样的结果了。被volatile修饰的写变量不能和之前的读写代码调整,这里我们当做这个关键字不存在,以后会有专门的篇幅去详细讲解这个关键字的,这个关键字的坑有许多,我们慢慢踩)

那我们到底的咋整啊?

public class MyObject {
private static MyObject myObject;
private MyObject(){ }
public static MyObject getInstance(){ if (myObject == null) {
MyObject my;
synchronized (MyObject.class) {
my = myObject;
if (my == null) {
synchronized (MyObject.class) {
if (my == null) {
my = new MyObject();
}
}
myObject = my;
}
}
}
return myObject;
}
}

  

我们在第一个同步块里面创建一个临时变量,然后使用这个临时变量进行对象的创建,并且在最后把myObject指针临时变量的内存空间。写出这种代码基于以下思想,即synchronized会起到一个代码屏蔽的作用,同步块里面的代码和外部的代码没有联系。因此,在外部的同步块里面对临时变量my进行操作并不影响myObject,所以外部类在myObject=my;之前检测myObject的时候,结果myObject依然是null。

由于同步块的释放保证在此之前——也就是同步块里面——的操作必须完成,但是并不保证同步块之后的操作不能因编译器优化而调换到同步块结束之前进行。因此,编译器完全可以把myObject=my;这句移到内部同步块里面执行。又错了。

4、内部类实现方式

  

public class MyObject_inner {
private static class MyObjectHandler{
private static MyObject_inner myObject_inner = new MyObject_inner();
}
private MyObject_inner(){}
public static MyObject_inner getInstance(){
return MyObjectHandler.myObject_inner;
}
}

在这一版本的单例模式实现代码中,我们使用了Java的静态内部类。这一技术是被JVM明确说明了的,因此不存在任何二义性。在这段代码中,因为Myobject_inner没有static的属性,因此并不会被初始化。直到调用getInstance()的时候,会首先加载MyObjectHandler类,这个类有一个static的MyObject_inne实例,因此需要调用MyObject_inne的构造方法,然后getInstance()将把这个内部类的myobject_inner返回给使用者。由于这个myobject_inner是static的,因此并不会构造多次。

由于MyObjectHandler是私有静态内部类,所以不会被其他类知道,同样,static语义也要求不会有多个实例存在。并且,JSL规范定义,类的构造必须是原子性的,非并发的,因此不需要加同步块。同样,由于这个构造是并发的,所以getInstance()也并不需要加同步。

但是这种情况完全是对的么?假如遇到序列化的对象呢?会是什么样的结果?

5、序列化与反序列化的单例模式的实现

静态内部类可以达到线程安全的问题,但是如果遇到序列化对象的时候,使用默认的方式运行得到的结果还是多例的。

具体是为什么在序列化的时候不是单例的,本人我掌握的不太好,后续会把其中涉及到的知识补充之后,在完善此篇文章。

序列化会通过反射调用无参数的构造方法创建一个新的对象。解决的方式就是在implements Serializable这个方法里面加上这段代码

  

import java.io.Serializable;

public class MyObject_inner implements Serializable{
private static final long seriaVersionUID = 8899L;
private static class MyObjectHandler{
private static MyObject_inner myObject_inner = new MyObject_inner();
}
private MyObject_inner(){}
public static MyObject_inner getInstance(){
return MyObjectHandler.myObject_inner;
} private Object readResolve(){
return MyObjectHandler.myObject_inner;
}
}

  

6、静态代码块实现单例模式

  

public class MyObject_inner {
private static MyObject_inner instance = null;
private MyObject_inner(){}
static {
instance = new MyObject_inner();
}
public static MyObject_inner getInstance(){
return instance;
}
}

  

7、枚举方法实现单例模式

  

public class MyObject_enum {
public enum EnumSingleton{
Instance;
private MyObject_enum instance;
EnumSingleton(){
instance = new MyObject_enum();
}
public MyObject_enum getInstance(){
return instance;
}
}
}

获取资源的方式很简单,只要 EnumSingleton.INSTANCE.getInstance() 即可获得所要实例。下面我们来看看单例是如何被保证的:

首先,在枚举中我们明确了构造方法限制为私有,在我们访问枚举实例时会执行构造方法,同时每个枚举实例都是static final类型的,也就表明只能被实例化一次。在调用构造方法时,我们的单例被实例化。也就是说,因为enum中的实例被保证只会被实例化一次,所以我们的INSTANCE也被保证实例化一次。枚举也提供了序列化机制。所以单元素的枚举类型已经成为了实现单例模式的最佳方法

  

这篇文章到此暂时先告一段落,里面有一点设计的序列化与反序列化实现单例模式我以后会在继续的更新补充进去的。 欢迎各位博友批评指正

  
 

  

  

  

JAVA_多线程_单例模式的更多相关文章

  1. java_设计模式_单例模式_Singleton Pattern(2016-08-04)

    概念: 单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例. 适用场景: 在计算机系统中,线程池.缓存.日志对象.对话框.打印机.显卡的驱动程序对象常被设计成单例.这些应用都或多或 ...

  2. java多线程与单例模式(Singleton)不得不说的故事

    转发自:http://blog.csdn.net/ligang7560/article/details/50890282 单例模式的多种实现方式 我们都知道单例模式有几种常用的写法: - 饿汉模式 - ...

  3. iOS开发多线程篇—单例模式(ARC)

    iOS开发多线程篇—单例模式(ARC) 一.简单说明: 设计模式:多年软件开发,总结出来的一套经验.方法和工具 java中有23种设计模式,在ios中最常用的是单例模式和代理模式. 二.单例模式说明 ...

  4. Java多线程_生产者消费者模式2

    在我的上一条博客中,已经介绍到了多线程的经典案列——生产者消费者模式,但是在上篇中用的是传统的麻烦的非阻塞队列实现的.在这篇博客中我将介绍另一种方式就是:用阻塞队列完成生产者消费者模式,可以使用多种阻 ...

  5. 多线程安全单例模式学习代码 c++11

    // Singleton.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include <mutex> #include & ...

  6. python的单例模式--解决多线程的单例模式失效

    单例模式 单例模式(Singleton Pattern) 是一种常用的软件设计模式,主要目的是确保某一个类只有一个实例存在.希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场 比如,某个 ...

  7. java多线程环境单例模式实现详解

    Abstract 在开发中,如果某个实例的创建需要消耗很多系统资源,那么我们通常会使用惰性加载机制,也就是说只有当使用到这个实例的时候才会创建这个实例,这个好处在单例模式中得到了广泛应用.这个机制在s ...

  8. java设计模式_单例模式

    懒汉式 非线程安全 特点:Lazy 初始化.非多线程安全.易实现 描述:这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程.因为没有加锁 synchronized,所以严格意义上它并不算单 ...

  9. java 多线程,单例模式类(创建对象)最优写法

    单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种模式涉及到一个单一的类,该类负责创 ...

随机推荐

  1. koa2 use里面的next到底是什么

    koa2短小精悍,女人不爱男人爱. 之前一只有用koa写一点小程序,自认为还吼吼哈,知道有一天某人问我,你说一下 koa或者express中间件的实现原理.然后我就支支吾吾,好久吃饭都不香. 那么了解 ...

  2. Kibana5 数据探索使用(Discover功能)

    认识Kibana Kibana 是一个为 Logstash 和 ElasticSearch 提供的日志分析的 Web 接口.可使用它对日志进行高效的搜索.可视化.分析等各种操作.Kibana的使用场景 ...

  3. Apache Kafka - 介绍

    原文地址地址: http://blogxinxiucan.sh1.newtouch.com/2017/07/12/Apache-Kafka-介绍/ Apache Kafka教程 之 Apache Ka ...

  4. 51nod 1130 N的阶乘的长度(斯特林近似)

    输入N求N的阶乘的10进制表示的长度.例如6! = 720,长度为3.   Input 第1行:一个数T,表示后面用作输入测试的数的数量.(1 <= T <= 1000) 第2 - T + ...

  5. git中常用的指令

    1.初始化仓库 git init2.设置用户名与邮箱 git config --global user.name 'name' git config --global user.email 'emai ...

  6. <Mastering KVM Virtualization>:第二章 KVM内部原理

    在本章中,我们将讨论libvirt.QEMU和KVM的重要数据结构和内部实现.然后,我们将深入了解KVM下vCPU的执行流程. 在这一章,我们将讨论: libvirt.QEMU和KVM的内部运作方式. ...

  7. mysql常用的hint

    --mysql常用的hint -------------------2014/06/26 对于经常使用oracle的朋友可能知道,oracle的hint功能种类很多,对于优化sql语句提供了很多方法. ...

  8. 关于DB2版本、补丁升级和回退的总结[转载]

    首先介绍几个概念 RELEASE的升级就是版本升级,例如9.1→9.5→9.7→10.1,可以跳版本升级,例如9.1→10.1 FIX PACK简称FP,就是打补丁,例如9.7.1→9.7.2,每个版 ...

  9. 实现QObject与JavaScript通讯(基于QWebEngine + QWebChannel)

    实现QObject与JavaScript通讯(基于QWebEngine + QWebChannel) 通过使用QtWebEngine加载相关页面,然后用QtWebChannel作为Qt与Javascr ...

  10. .Net Core下使用WCF

    在.net core 下的wcf 和framework下的wcf使用方式有点不太一样.在core下用wc,需要安装VS扩展Visual Studio WCF Connected Service,目前这 ...