如何保证单例模式在多线程中的线程安全性

对大数据、分布式、高并发等知识的学习必须要有多线程的基础。这里讨论一下如何在多线程的情况下设计单例模式。在23中设计模式中单例模式是比较常见的,在非多线程的情况下写单例模式,考虑的东西会很少,但是如果将多线程和单例模式结合起来,考虑的事情就变多了,如果使用不当(特别是在生成环境中)就会造成严重的后果。所以如何使单例模式在多线程中是安全的显得尤为重要,下面介绍各个方式的优缺点以及可用性:

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

立即加载模式就是在调用getInstance()方法前,实例就被创建了,例:

public class MyObject {
 // 立即加载方式  ==饿汉模式
private static MyObject myObject=new MyObject();
private MyObject(){
}
public static MyObject getInstance(){
return myObject;
}
}

-------------------------------------------------------------------

public class MyThread extends Thread{
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();
}
}

控制台打印:

714682869
714682869
714682869

控制台打印出3个相同的hashCode,说明只有一个对象,这就是立即加载的单例模式。但是这种模式有一个缺点,就是不能有其他的实例变量,因为getInstance()方法没有同步,所以可能出现非线程安全问题。

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

延迟加载就是在getInstance()方法中创建实例,例:

public class MyObject {
private static MyObject myObject;
private MyObject(){
     }
 public static MyObject getInstance(){
// 延迟加载
 if(myObject!=null){  
}else{
myObject=new MyObject();
 }
return myObject;
     }
}

-------------------------------------------------------------------

public class MyThread extends Thread{
public void run(){
System.out.println(MyObject.getInstance().hashCode());
}
}

-------------------------------------------------------------------

public class Run {
public static void main(String[] args) {
MyThread t1=new MyThread();
t1.start();
}
}

控制台打印:

1701381926

控制台打印出一个实例。缺点:在多线程的环境中,就会出现取多个实例的情况,与单例模式的初衷相背离。所以在多线程的环境中,此实例代码是错误的。

3.延迟加载中使用synchronized修饰方法

public class MyObject {
private static MyObject myObject;
private MyObject(){
}
synchronized public static MyObject getInstance(){
try {
if(myObject!=null){
}else{
Thread.sleep(3000);
myObject=new MyObject();
}
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
return myObject;
}
}

-------------------------------------------------------------------

public class MyThread extends Thread{
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();
}
}

控制台打印:

1069480624
1069480624
1069480624

虽然得到了相同的实例,但是我们知道synchronized是同步的,一个线程必须等待另一个线程释放锁之后才能执行,影响了效率。

4.延迟加载中使用同步代码块,对类加锁

public class MyObject {
private static MyObject myObject;
private MyObject(){
}
public static MyObject getInstance(){
try {
synchronized(MyObject.class){
if(myObject!=null){
}else{
Thread.sleep(3000);
myObject=new MyObject();
}
}
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
return myObject;
}
}

-------------------------------------------------------------------

public class MyThread extends Thread {
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();
}
}

控制台打印:

1743911840
1743911840
1743911840

此代码虽然是正确的,但getInstance()方法里的代码都是同步的了,其实也和第三种方式一样会降低效率

5.使用DCL双检查锁机制

DCL双检查锁机制即使用volatile关键字(使变量在多个线程中可见)修改对象和synchronized代码块

public class MyObject {
    private volatile static MyObject myObject;
    private MyObject(){
    }
    public static MyObject getInstance(){
    try {
if(myObject!=null){
}else{
Thread.sleep(3000);
synchronized(MyObject.class){
if(myObject==null){
myObject=new MyObject();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
// TODO: handle exception
}
    return myObject;
    }
}

-------------------------------------------------------------------

public class MyThread extends Thread {
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();
}
}

控制台打印:

798941612
798941612
798941612

使用DCL双检查锁机制,成功解决了延迟加载模式中遇到的多线程问题,实现了线程安全。其实大多数多线程结合单例模式情况下使用DCL是一种好的解决方案。

6.使用静态内置类实现单例模式

public class MyObject {
// 内部类方式
private static class MyObjectHandler{
private static MyObject myObject=new MyObject();
}
private MyObject(){

}
public static MyObject getInstance(){
return MyObjectHandler.myObject;
}
}

-------------------------------------------------------------------

public class MyThread extends Thread {
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();

}
}

控制台打印:

1743911840
1743911840
1743911840

使用静态内置类可以解决多线程中单例模式的非线程安全的问题,实现线程安全,但是如果对象是序列化的就无法达到效果了。

7.序列化与反序列化的单例模式

需要readResolve方法

public class MyObject implements Serializable{
private static final long serialVersionUID=888L;
// 内部类
private static class MyObjectHandler{
private static final MyObject myObject=new MyObject();
}
private MyObject(){

}
public static MyObject getInstance(){
return MyObjectHandler.myObject;
}
 protected Object readResolve() throws ObjectStreamException {
 System.out.println("调用了readResolve方法");
return MyObjectHandler.myObject;
 }
}

-------------------------------------------------------------------

public class SaveAndRead {
public static void main(String[] args) {
try {
MyObject myObject=MyObject.getInstance();
FileOutputStream fosRef=new FileOutputStream(new File("myObjectFile.txt"));
ObjectOutputStream oosRef=new ObjectOutputStream(fosRef);
oosRef.writeObject(myObject);
oosRef.close();
fosRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
// TODO: handle exception
} catch(IOException e){
e.printStackTrace();
}
try {
FileInputStream fisRef=new FileInputStream(new File("myObjectFile.txt"));
ObjectInputStream iosRef=new ObjectInputStream(fisRef);
MyObject myObject=(MyObject) iosRef.readObject();
iosRef.close();
fisRef.close();
System.out.println(myObject.hashCode());
} catch (FileNotFoundException e) {
// TODO: handle exception
} catch(IOException e){
e.printStackTrace();
} catch(ClassNotFoundException e){
e.printStackTrace();
}
}
}

控制台打印:

1988716027
调用了readResolve方法
1988716027

调用了readResolve方法后就是单例了,如果我们注释掉readResolve方法,

控制台打印:

977199748
536468534

8.使用static代码块实现单例模式

public class MyObject {
private static MyObject instance=null;
private MyObject(){

}
static {
instance=new MyObject();
}
public static MyObject getInstance(){
return instance;
}
}

-------------------------------------------------------------------

public class MyThread extends Thread{

public void run(){
for (int i = 0; i <5; i++) {
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();

}
}

控制台打印:

798941612
798941612
798941612

https://blog.csdn.net/gan785160627/article/details/81946242

应用单例模式时,类只能有一个对象实例,这么做的目的是避免不一致状态。

饿汉式单例:(立即加载)

  1. // 饿汉式单例

  2.  

    public class Singleton1 {

  3.  

  4.  

    // 指向自己实例的私有静态引用,主动创建

  5.  

    private static Singleton1 singleton1 = new Singleton1();

  6.  

  7.  

    // 私有的构造方法

  8.  

    private Singleton1(){}

  9.  

  10.  

    // 以自己实例为返回值的静态的公有方法,静态工厂方法

  11.  

    public static Singleton1 getSingleton1(){

  12.  

    return singleton1;

  13.  

    }

  14.  

    }

懒汉式单例:(延迟加载)

  1. // 懒汉式单例

  2.  

    public class Singleton2 {

  3.  

  4.  

    // 指向自己实例的私有静态引用

  5.  

    private static Singleton2 singleton2;

  6.  

  7.  

    // 私有的构造方法

  8.  

    private Singleton2(){}

  9.  

  10.  

    // 以自己实例为返回值的静态的公有方法,静态工厂方法

  11.  

    public static Singleton2 getSingleton2(){

  12.  

    // 被动创建,在真正需要使用时才去创建

  13.  

    if (singleton2 == null) {

  14.  

    singleton2 = new Singleton2();

  15.  

    }

  16.  

    return singleton2;

  17.  

    }

  18.  

    }

多线程下线程安全的懒汉式单例(饿汉式本身是线程安全的):

1)、同步延迟加载 — synchronized方法

  1. // 线程安全的懒汉式单例

  2.  

    public class Singleton2 {

  3.  

  4.  

    private static Singleton2 singleton2;

  5.  

  6.  

    private Singleton2(){}

  7.  

  8.  

    // 使用 synchronized 修饰,临界资源的同步互斥访问

  9.  

    public static synchronized Singleton2 getSingleton2(){

  10.  

    if (singleton2 == null) {

  11.  

    singleton2 = new Singleton2();

  12.  

    }

  13.  

    return singleton2;

  14.  

    }

  15.  

    }

2)、同步延迟加载 — synchronized块

  1. // 线程安全的懒汉式单例

  2.  

    public class Singleton2 {

  3.  

  4.  

    private static Singleton2 singleton2;

  5.  

  6.  

    private Singleton2(){}

  7.  

  8.  

  9.  

    public static Singleton2 getSingleton2(){

  10.  

    synchronized(Singleton2.class){ // 使用 synchronized 块,临界资源的同步互斥访问

  11.  

    if (singleton2 == null) {

  12.  

    singleton2 = new Singleton2();

  13.  

    }

  14.  

    }

  15.  

    return singleton2;

  16.  

    }

  17.  

    }

3)、同步延迟加载 — 使用内部类实现延迟加载

  1. // 线程安全的懒汉式单例

  2.  

    public class Singleton5 {

  3.  

  4.  

    // 私有内部类,按需加载,用时加载,也就是延迟加载

  5.  

    private static class Holder {

  6.  

    private static Singleton5 singleton5 = new Singleton5();

  7.  

    }

  8.  

  9.  

    private Singleton5() {

  10.  

  11.  

    }

  12.  

  13.  

    public static Singleton5 getSingleton5() {

  14.  

    return Holder.singleton5;

  15.  

    }

  16.  

    }

4)双重检测

  1. // 线程安全的懒汉式单例

  2.  

    public class Singleton3 {

  3.  

  4.  

    //使用volatile关键字防止重排序,因为 new Instance()是一个非原子操作,可能创建一个不完整的实例

  5.  

    private static volatile Singleton3 singleton3;

  6.  

  7.  

    private Singleton3() {

  8.  

    }

  9.  

  10.  

    public static Singleton3 getSingleton3() {

  11.  

    // Double-Check idiom

  12.  

    if (singleton3 == null) {

  13.  

    synchronized (Singleton3.class) { // 1

  14.  

    // 只需在第一次创建实例时才同步

  15.  

    if (singleton3 == null) { // 2

  16.  

    singleton3 = new Singleton3(); // 3

  17.  

    }

  18.  

    }

  19.  

    }

  20.  

    return singleton3;

  21.  

    }

  22.  

    }

5)ThreadLocal

  1. public class Singleton {

  2.  

  3.  

    // ThreadLocal 线程局部变量,将单例instance线程私有化

  4.  

    private static ThreadLocal<Singleton> threadlocal = new ThreadLocal<Singleton>();

  5.  

    private static Singleton instance;

  6.  

  7.  

    private Singleton() {

  8.  

  9.  

    }

  10.  

  11.  

    public static Singleton getInstance() {

  12.  

  13.  

    // 第一次检查:若线程第一次访问,则进入if语句块;否则,若线程已经访问过,则直接返回ThreadLocal中的值

  14.  

    if (threadlocal.get() == null) {

  15.  

    synchronized (Singleton.class) {

  16.  

    if (instance == null) { // 第二次检查:该单例是否被创建

  17.  

    instance = new Singleton();

  18.  

    }

  19.  

    }

  20.  

    threadlocal.set(instance); // 将单例放入ThreadLocal中

  21.  

    }

  22.  

    return threadlocal.get();

  23.  

    }

  24.  

    }

原文地址:https://www.cnblogs.com/whymoney1000/p/11420593.html

时间: 2024-10-09 12:10:20

如何保证单例模式在多线程中的线程安全性的相关文章

单例模式在多线程中的使用情况

废话不多说,直接上代码: class MyThreadScopeData{ private MyThreadScopeData(){} private static MyThreadScopeData instance; //单例设计模式 public static MyThreadScopeData getInstance(){ if(instance ==null){ instance = new MyThreadScopeData(); } return instance; } } 上述代

关于java多线程中守护线程的理解

在java中有两类线程:User Thread(用户线程).Daemon Thread(守护线程) 守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程.下面的方法就是用来设置守护线程的. 1 Thread daemonTread = new Thread(); 2 3 // 设定 daemonThread 为 守护线程,default false(非守护线程) 4 daemonThread.setDaemon(true); 5 6 // 验证当前线程是否为守护线程,返回 tr

单例模式与多线程

概述 关于一般单例模式的创建和分析在我的另一篇博客<Java设计模式--单件模式>中有详细说明.只是在上篇博客中的单例是针对于单线程的操作,而对于多线程却并不适用,本文就从单例模式与多线程安全的角度出发,讲解单例模式在多线程中应该如何被使用. 版权说明 著作权归作者所有. 商业转载请联系作者获得授权,非商业转载请注明出处. 本文作者:Coding-Naga 发表日期: 2016年4月6日 本文链接:http://blog.csdn.net/lemon_tree12138/article/det

Spring中如何获取request的方法汇总及其线程安全性分析

前言 本文将介绍在Spring MVC开发的web系统中,获取request对象的几种方法,并讨论其线程安全性.下面话不多说了,来一起看看详细的介绍吧. 概述 在使用Spring MVC开发Web系统时,经常需要在处理请求时使用request对象,比如获取客户端ip地址.请求的url.header中的属性(如cookie.授权信息).body中的数据等.由于在Spring MVC中,处理请求的Controller.Service等对象都是单例的,因此获取request对象时最需要注意的问题,便是

VC中利用多线程技术实现线程之间的通信

文章来源:[url]http://www.programfan.com/article/showarticle.asp?id=2951[/url] 当前流行的Windows操作系统能同时运行几个程序(独立运行的程序又称之为进程),对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力.用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义.现在的大型应用软件无一不是多线程多任务处理,单线程的软件是不可想象的.

谈谈多线程开发中的线程和任务的理念

前段时间写了一个iOS端的数据统计SDK,数据统计有些复杂的计算和数据上报操作.由于有些操作比較耗时.所以不得不在后台线程进行操作,由此引发了我对多线程的思考,在iOS开发中,一般非常难再见到直接使用NSThread进行多线程编程的了.由于苹果提供了另外几种多线程开发的解决方式.而这些解决方式面向的不再是线程,而是面向的是任务,以下就来以iOS和Android为例简单谈谈的线程和任务的相关概念. 线程: 我们在学习基础的编程语言的时候,肯定听说过线程的概念,线程是操作系统调度的最小单位,共享进程

C#中的线程(三) 使用多线程

第三部分:使用多线程 1.  单元模式和Windows Forms 单元模式线程是一个自动线程安全机制, 非常贴近于COM——Microsoft的遗留下的组件对象模型.尽管.NET最大地放弃摆脱了遗留下的模型,但很多时候它也会突然出现,这是因为有必要 与旧的API 进行通信.单元模式线程与Windows Forms最相关,因为大多Windows Forms使用或包装了长期存在的Win32 API——连同它的单元传统. 单元是多线程的逻辑上的“容器”,单元产生两种容量——“单的”和“多的”.单线

在多线程中如何保证集合的安全

线程和进程 进程(Process)的概念.狭义的进程是正在运行的程序的实例:广义的进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动,是操作系统动态执行的基本单元. 线程(Thread),有时被称为轻量级进程(LWP),是程序执行流的最小单位:一个标准的线程由线程ID.当前指令指针(PC).寄存器集合和堆栈组成. 通常情况下,一个进程由一个到多个线程组成,各个线程之间共享程序的内存空间及一些进程级的资源. 在大多数软件应用中,线程的数量都不止一个,多线程程序处在一个多变的环境中,可访

Java多线程中线程间的通信

一.使用while方式来实现线程之间的通信 package com.ietree.multithread.sync; import java.util.ArrayList; import java.util.List; public class MyList { private volatile static List list = new ArrayList(); public void add() { list.add("apple"); } public int size() {