日常小结-多线程的单例模式的三种实现方式

11 篇文章 0 订阅
4 篇文章 0 订阅

多线程单例模式在很多并发的书里面都有写。这里做一个简单的总结。主要内容来自《java并发编程的艺术》《java多线程编程核心》

单例模式的分类

饿汉模式:类初始化的时候就进行创建单例模式
懒汉模式:在调用getinstance方法的时候才创建单例;

首先饿汉模式是不会出现任何问题的。而且在大多数情况下饿汉模式要由于懒汉模式。只有在少数情况下,增快应用启动速度或者减少初始化延迟的时候才使用懒汉模式。典型的懒汉模式的解决方案是DCL。除此之外还有一种方案是使用静态类初始化方案。

饿汉模式

饿汉模式很简单在静态域内进行初始化。不会出现任何问题

package test;
public class MyObject{
    private volatile static MyObject myObject = new MyObject();
    private MyObject(){};
}

类初始化的过程可详见《深入理解java虚拟机》。

懒汉模式

在多线程的情况下可能会有多个线程同时调用getinstance这样会导致创建多个实例。

解决方案

synchronized

synchronized方法来同步方法或者同步整个getinstance。
当然是有效的只是效率比较低。
需要说明的是synchronized方法只对new方法进行同步是不可行的,因为在检测null之后可能会有线程切换。

DCL双重检测锁

首先对UniqueInstance引用加速volatile变量。
然后在在对new函数进行同步,并进行双重检测,这是目前通用的高效解决方案。

下面贴一下代码:

package test;
public class MyObject{
    private volatile static MyObject myObject;
    private MyObject(){};

    public static MyObject getInstance(){
        try{
            if(myObject == null){
                Synchronized(MyObject.class){
                    if(myObject == null){
                        myObject = new MyObejct();
                    }
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    return myObject;
    }
}
package test;
public class MyThread extends Thread{
    @Override
    public void run(){
        System.out.println(MyObject.getInstance().hashCode());
    }
}
public class Run{
    public static void main(String[] args){
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        MyThread t3 = new MyThread();
        t1.start();
        t2.start();
        t3.start();
    }
}

类初始化的解决方案

从深层次的角度说之所以出现单例模式失效的原因不仅仅是因为线程间的切换,还有是因为编译器和处理器在执行java代码的时候会进行重排序。从而导致没有良好同步的语句之间代码的执行顺序不是按照程序员预想的那样。
使用类初始化的原理就是在执行类初始化的过程中,当前线程需要获得类初始化的锁。这样只有获得了类初始化的锁的程序才能进行初始化。及时在本线程内初始化的语句产生了重排序但是在获得锁和释放锁之间的程序属于临界区内,对外界而言是不可见的。因此也不会产生多个实例的结果。
简而言之,如果在类的内部构造一个静态的类,那这个类的初始化只会由一个获得了这个类初始化锁的进程执行。
代码如下:

package test;
public class InstanceFactory{
    private static class InstanceHolder{
        public static Instance instance = new Instance();
    }

    public static Instance getInstance(){
        return InstanceHolder.isntance;
    }

这样的类初始化方案显得比较简洁,但是相比于volatile的方案这样的初始化只能进行静态类变量的访问,对于实例变量的内容则无能无力了

遇到的坑

本来这方面的内容应该在稍后的时间看,但是最近做东西遇到了这个问题。
最近在用netty写一个im的程序。这里面用到数据。我把数据库的连接专门建了一个类然后用单例模式进行管理,但是后来发现不可用。找了很久没发现问题,后来才查到这方面的资料。
本来问题应该已经解决了,但是我在写这个多线程的demo的时候出现了始终没办法用,主要原因还是有个基本的问题没有解决。
通常来说多线程单例模式的情况下是4个类,一个单例模式本身的类,一个tes1和test2用来调用getInstance,一个test3用来运行test1和test2。但是我在用的时候没有写test3。而是test1和test2直接运行。总是会创建两个实例,原因很简单。所谓单例模式也好,或者任何其他内存一致性问题通常来说都是要在一个进程内的多个线程之间的共享内存及通信问题。但是直接运行test1和test2则可以看成是两个进程。而使用test3调用则是一个进程。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值