JAVA_多线程_单例模式

  这篇是入职之后的第二篇了,上一篇我简单介绍了一下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;这句移到内部同步块里面执行。又错了。

  3、内部类实现方式

  

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()也并不需要加同步。

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

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

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

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

  序列化会通过反射调用无参数的构造方法创建一个新的对象。解决的方式就是在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;
    }
}

  

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

  

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;
    }
}

  

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

  

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也被保证实例化一次。枚举也提供了序列化机制。所以单元素的枚举类型已经成为了实现单例模式的最佳方法

  

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

  

  

  

  

时间: 2024-11-07 03:01:08

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

java_多线程_生产者与消费者(并发协作)

对于多线程程序来说,不管任何编程语言,生产者和消费者模型都是最经典的.就像学习每一门编程语言一样,Hello World!都是最经典的例子. 实际上,准确说应该是“生产者-消费者-仓储”模型,离开了仓储,生产者消费者模型就显得没有说服力了.对于此模型,应该明确一下几点:1.生产者仅仅在仓储未满时候生产,仓满则停止生产.2.消费者仅仅在仓储有产品时候才能消费,仓空则等待.3.当消费者发现仓储没产品可消费时候会通知生产者生产.4.生产者在生产出可消费产品时候,应该通知等待的消费者去消费. packa

Java_多线程_成员变量与局部变量

线程会共享进程范围内的资源,例如内存句柄和文件句柄,但每个线程都有各自的程序计数器.栈及局部变量等.线程还提供了一种直观的分解模式来充分利用多处理器系统中的硬件并行性,而在同一个程序中的多个线程还可以被同时调度到多个CPU上运行.线程也被称为轻量级进程.在大多数现代操作系统中,都是以线程为基本的调度单位,而不是进程.同一个进程中的所有线程都将共享进程的内存地址空间,因此这些线程都能访问相同的成员变量并在同一个堆上分配对象. 成员变量: package deep; public class Mem

多线程_创建线程_继承Thread类

public class ThreadDemo {   public static void main(String[] args){         Demo d = new Demo();   d.start();      for(int i = 0;i < 100;i++){      System.out.println("MainThread" + i);   }   } } class Demo extends Thread {   public void run(

IOS_多线程_售票

H:/1007/01_多线程_大任务_MainViewController.m // MainViewController.m // 多线程-01.大任务 // Created by apple on 13-10-7. #import "MainViewController.h" @interface MainViewController () @property (weak, nonatomic) UIImageView *imageView; @end @implementatio

iOS多线程技术—单例模式(ARC)与(MRC)

iOS多线程技术—单例模式(ARC) 一.简单说明: 设计模式:多年软件开发,总结出来的一套经验.方法和工具 java中有23种设计模式,在ios中最常用的是单例模式和代理模式. 二.单例模式说明 (1)单例模式的作用 :可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问,从而方便地控制了实例个数,并节约系统资源. (2)单例模式的使用场合:在整个应用程序中,共享一份资源(这份资源只需要创建初始化1次),应该让这个类创建出来的对象永远只有一个. (3)单例模式在ARC\MRC环

iOS开发多线程篇—单例模式(ARC) - 文顶顶

原文  http://www.cnblogs.com/wendingding/p/3808641.html iOS开发多线程篇—单例模式(ARC) 一.简单说明: 设计模式:多年软件开发,总结出来的一套经验.方法和工具 java中有23种设计模式,在ios中最常用的是单例模式和代理模式. 二.单例模式说明 (1)单例模式的作用 :可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问,从而方便地控制了实例个数,并节约系统资源. (2)单例模式的使用场合:在整个应用程序中,共享一份资

iOS开发多线程篇—单例模式(MRC)

iOS开发多线程篇—单例模式(MRC)  一.非ARC模式下的单例模式 1.说明:把一个项目修改为非ARC的 2.MAC下单例模式代码示例: 新建一个工具类,让该类继承自NSObject. YYAudioTool.m文件 1 // 2 // YYAudioTool.m 3 // 06-单例模式1 4 // 5 // Created by apple on 14-6-25. 6 // Copyright (c) 2014年 itcase. All rights reserved. 7 // 8 9

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

iOS开发多线程篇—单例模式(ARC) - 文顶顶 原文  http://www.cnblogs.com/wendingding/p/3808641.html iOS开发多线程篇—单例模式(ARC) 一.简单说明: 设计模式:多年软件开发,总结出来的一套经验.方法和工具 java中有23种设计模式,在ios中最常用的是单例模式和代理模式. 二.单例模式说明 (1)单例模式的作用 :可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问,从而方便地控制了实例个数,并节约系统资源. (

多线程下单例模式:懒加载(延迟加载)和即时加载

在开发中,如果某个实例的创建需要消耗很多系统资源,那么我们通常会使用惰性加载机制,也就是说只有当使用到这个实例的时候才会创建这个实例,这个好处在单例模式中得到了广泛应用.这个机制在single-threaded环境下的实现非常简单,然而在multi-threaded环境下却存在隐患.本文重点介绍惰性加载机制以及其在多线程环境下的使用方法.(作者numberzero,参考IBM文章<Double-checked locking and the Singleton pattern>,欢迎转载与讨论