java多线程之线程安全的单例模式_java

概念:
  java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例三种。
  单例模式有一下特点:
  1、单例类只能有一个实例。
  2、单例类必须自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。
  单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

这里主要详细介绍两种:懒汉式和饿汉式

一、立即加载/饿汉式

在调用方法前,实例就已经被创建,代码:

package com.weishiyao.learn.day8.singleton.ep1;

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

  private MyObject() {
  }

  public static MyObject getInstance() {
    // 此代码版本为立即加载
    // 此版本代码的缺点是不能有其他实例变量
    // 因为getInstance()方法没有同步
    // 所以有可能出现非线程安全的问题
    return myObject;
  }
}

创建线程类

package com.weishiyao.learn.day8.singleton.ep1;

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

创建运行类

package com.weishiyao.learn.day8.singleton.ep1;

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

运行结果
 167772895
 167772895
 167772895
hashCode是同一个值,说明对象也是同一个,说明实现了立即加载型的单利模式

二、延迟加载/懒汉式

在调用方法以后实例才会被创建,实现方案可以是将实例化放到无参构造函数当中,这样只有当调用的时候才会创建对象的实例,代码:

package com.weishiyao.learn.day8.singleton.ep2;

public class MyObject {
  private static MyObject myObject;

  private MyObject() {

  }

  public static MyObject getInstance() {
    // 延迟加载
    if (myObject != null) {

    } else {
      myObject = new MyObject();
    }
    return myObject;
  }
}

创建线程类

package com.weishiyao.learn.day8.singleton.ep2;

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

创建运行类

package com.weishiyao.learn.day8.singleton.ep2;

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

运行结果

167772895

这样虽然取出了一个对象的实例,但是如果在多线程的环境中,就会出现多个实例的情况,这样就不是单例模式了

运行测试类

package com.weishiyao.learn.day8.singleton.ep2;

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

运行结果

980258163
1224717057
1851889404
188820504
1672864109
既然出现问题,就要解决问题,在懒汉模式中的多线程的解决方案,代码:

第一种方案,最常见的,加synchronized,而synchronized可以加到不同的位置

第一种,方法锁

package com.weishiyao.learn.day8.singleton.ep3;

public class MyObject {
  private static MyObject myObject;

  private MyObject() {

  }

  synchronized public static MyObject getInstance() {
    // 延迟加载
    try {
      if (myObject != null) {

      } else {
        // 模拟在创建对象之前做一些准备性的工作
        Thread.sleep(2000);
        myObject = new MyObject();
      }

    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return myObject;
  }
}

这种synchronized的同步方案导致效率过于低下,整个方法都被锁住

第二种synchronized使用方案

package com.weishiyao.learn.day8.singleton.ep3;

public class MyObject {
  private static MyObject myObject;

  private MyObject() {

  }

  public static MyObject getInstance() {
    // 延迟加载
    try {
      synchronized (MyObject.class) {
        if (myObject != null) {

        } else {
          // 模拟在创建对象之前做一些准备性的工作
          Thread.sleep(2000);
          myObject = new MyObject();
        }
      }

    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return myObject;
  }
}

这种方法效率一样很低,方法内的所有代码都被锁住,只需要锁住关键代码就好,第三种synchronized使用方案
package com.weishiyao.learn.day8.singleton.ep3;

public class MyObject {
  private static MyObject myObject;

  private MyObject() {

  }

  public static MyObject getInstance() {
    // 延迟加载
    try {
        if (myObject != null) {

        } else {
          // 模拟在创建对象之前做一些准备性的工作
          Thread.sleep(2000);
          synchronized (MyObject.class) {
            myObject = new MyObject();
          }
      }

    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return myObject;
  }
}

这么写看似是最优方案了,但是,运行一下结果,发现,其实它是非线程安全的

结果:

1224717057
971173439
1851889404
1224717057
1672864109
Why?

虽然锁住了对象创建的语句,每次只能有一个线程完成创建,但是,当第一个线程进来创建完成Object对象以后,第二个线程进来还是可以继续创建的,因为我们紧紧只锁住了创建语句,这个问题解决方案

package com.weishiyao.learn.day8.singleton.ep3;

public class MyObject {
  private static MyObject myObject;

  private MyObject() {

  }

  public static MyObject getInstance() {
    // 延迟加载
    try {
        if (myObject != null) {

        } else {
          // 模拟在创建对象之前做一些准备性的工作
          Thread.sleep(2000);
          synchronized (MyObject.class) {
            if (myObject == null) {
              myObject = new MyObject();
            }
          }
      }

    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    return myObject;
  }
}

只需要在锁里面再添加一个判断,就可以保证单例了,这个是DCL双检查机制

结果如下:

1224717057
1224717057
1224717057
1224717057
1224717057
 三、使用内置静态类实现单例

主要代码

package com.weishiyao.learn.day8.singleton.ep4;

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

  public MyObject() {
  }

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

线程类代码

package com.weishiyao.learn.day8.singleton.ep4;

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

运行类

package com.weishiyao.learn.day8.singleton.ep4;

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

结果

1851889404
1851889404
1851889404
1851889404
1851889404
通过内部静态类,得到了线程安全的单例模式

四、序列化和反序列化单例模式

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

MyObject代码

package com.weishiyao.learn.day8.singleton.ep5;

import java.io.Serializable;

public class MyObject implements Serializable {

  /**
   *
   */
  private static final long serialVersionUID = 888L;

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

  public MyObject() {
  }

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

//  protected MyObject readResolve() {
//    System.out.println("调用了readResolve方法!");
//    return MyObjectHandler.myObject;
//  }
}

业务类

package com.weishiyao.learn.day8.singleton.ep5;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

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) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
    FileInputStream fisRef;
    try {
      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) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }

  }
}

结果

 970928725
 1099149023
两个不同的hashCode,证明并不是同一个对象,解决方案,添加下面这段代码

 protected MyObject readResolve() {
    System.out.println("调用了readResolve方法!");
    return MyObjectHandler.myObject;
  }

在反序列化的时候调用,可以得到同一个对象

 System.out.println(myObject.readResolve().hashCode());
结果

 1255301379
 调用了readResolve方法!
 1255301379
相同的hashCode,证明得到了同一个对象

五、使用static代码块实现单例

静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码快这个特性来实现单利模式

MyObject类

package com.weishiyao.learn.day8.singleton.ep6;

public class MyObject {
  private static MyObject instance = null;

  private MyObject() {
    super();
  }

  static {
    instance = new MyObject();
  }

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

线程类

package com.weishiyao.learn.day8.singleton.ep6;

public class MyThread extends Thread {
  @Override
  public void run() {
    for (int i = 0; i < 5; i++) {
      System.out.println(MyObject.getInstance().hashCode());
    }
  }
}

运行类

package com.weishiyao.learn.day8.singleton.ep6;

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

运行结果:

1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
1678885403
通过静态代码块只执行一次的特性也成功的得到了线程安全的单例模式

六、使用enum枚举数据类型实现单例模式

枚举enum和静态代码块的特性类似,在使用枚举时,构造方法会被自动调用,也可以用来实现单例模式

MyObject类

package com.weishiyao.learn.day8.singleton.ep7;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public enum MyObject {
  connectionFactory;

  private Connection connection;

  private MyObject() {
    try {
      System.out.println("调用了MyObject的构造");
      String url = "jdbc:mysql://172.16.221.19:3306/wechat_1?useUnicode=true&characterEncoding=UTF-8";
      String name = "root";
      String password = "111111";
      String driverName = "com.mysql.jdbc.Driver";
      Class.forName(driverName);
      connection = DriverManager.getConnection(url, name, password);
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    } catch (SQLException e) {
      e.printStackTrace();
    }
  }

  public Connection getConnection() {
    return connection;
  }
}

线程类

package com.weishiyao.learn.day8.singleton.ep7;

public class MyThread extends Thread {
  @Override
  public void run() {
    for (int i = 0; i < 5; i++) {
      System.out.println(MyObject.connectionFactory.getConnection().hashCode());
    }
  }
}

运行类

package com.weishiyao.learn.day8.singleton.ep7;

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

运行结果

调用了MyObject的构造
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
56823666
上面这种写法将枚举类暴露了,违反了“职责单一原则”,可以使用一个类将枚举包裹起来

package com.weishiyao.learn.day8.singleton.ep8;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class MyObject {

  public enum MyEnumSingleton {
    connectionFactory;

    private Connection connection;

    private MyEnumSingleton() {
      try {
        System.out.println("调用了MyObject的构造");
        String url = "jdbc:mysql://172.16.221.19:3306/wechat_1?useUnicode=true&characterEncoding=UTF-8";
        String name = "root";
        String password = "111111";
        String driverName = "com.mysql.jdbc.Driver";
        Class.forName(driverName);
        connection = DriverManager.getConnection(url, name, password);
      } catch (ClassNotFoundException e) {
        e.printStackTrace();
      } catch (SQLException e) {
        e.printStackTrace();
      }
    }

    public Connection getConnection() {
      return connection;
    }
  }

  public static Connection getConnection() {
    return MyEnumSingleton.connectionFactory.getConnection();
  }
}

更改线程代码

package com.weishiyao.learn.day8.singleton.ep8;

public class MyThread extends Thread {
  @Override
  public void run() {
    for (int i = 0; i < 5; i++) {
      System.out.println(MyObject.getConnection().hashCode());
    }
  }
}

结果
调用了MyObject的构造
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121
1948356121

以上总结了单利模式与多线程结合时遇到的各种情况和解决方案,以供以后使用时查阅。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索java多线程
, java单例模式
java线程安全
java单例模式 多线程、多线程单例模式、java 单例 多线程、多线程下的单例模式、c 单例模式 多线程,以便于您获取更多的相关知识。

时间: 2024-10-06 02:10:24

java多线程之线程安全的单例模式_java的相关文章

学习Java多线程之线程定义、状态和属性_java

一 .线程和进程 1. 什么是线程和进程的区别: 线程是指程序在执行过程中,能够执行程序代码的一个执行单元.在java语言中,线程有四种状态:运行 .就绪.挂起和结束. 进程是指一段正在执行的程序.而线程有事也被成为轻量级的进程,他得程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内功空间(代码段.数据段和堆空间)及一些进程级的资源(例如打开的文件),但是各个线程都拥有自己的棧空间. 2. 为何要使用多进程 在操作系统级别上来看主要有以下几个方面: - 使用多线程可以减少程序

java多线程和并发包入门示例_java

一.java多线程基本入门java多线程编程还是比较重要的,在实际业务开发中经常要遇到这个问题. java多线程,传统创建线程的方式有两种. 1.继承自Thread类,覆写run方法. 2.实现Runnable接口,实现run方法. 启动线程的方法都是调用start方法,真正执行调用的是run方法.参考代码如下: 复制代码 代码如下: package com.jack.thread; /** * 线程简单演示例子程序 *  * @author pinefantasy * @since 2013-

Java多线程之线程间协作 notify与wait的使用

(转载请注明出处:http://blog.csdn.net/buptgshengod) 1.背景        Java多线程操作运用很广,特别是在android程序方面.线程异步协作是多线程操作的难点也是关键,也是找工作面试经常考到的地方.下面分享一下我的使用心得. 介绍几个关键字: synchronized:线程锁,使得系统只执行当前线程. notifyAll():唤醒其它被锁住的线程 wait():挂起线程 ExecutorService exec=Executors.newCachedT

java多线程入门知识及示例程序_java

为什么需要多线程?模型的简化,如某些程序是由多个相对独立任务的运行: 图形界面的出现,输入.输出的阻塞 多核CPU的更好利用 异步行为的需要 Java多线程的特性: 程序的入口main本身是一个线程 线程是并发的,无序执行的 线程内部是顺序执行的 共享数据 Java多线程的风险: 安全风险:由于线程的操作顺序是不确定的,某些在单线程下能运行的程序到多线程下会出现意外的结果. 性能风险:服务器的吞吐量.响应性.资源消耗 Java多线程API: Java可以通过两种形式创建线程:一.实现Runnab

java多线程编程之Synchronized块同步方法_java

文章分享了4个例子对synchronized的详细解释 1.是否加synchronized关键字的不同 public class ThreadTest { public static void main(String[] args) { Example example = new Example(); Thread t1 = new Thread1(example); Thread t2 = new Thread1(example); t1.start(); t2.start(); } } cl

Java 多线程实例讲解(一)_java

Java多线程(一) 多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的. 一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,首先看一下下面这张较为经典的图: 上图中基本上囊括了Java中多线程各重要知识点.掌握了上图中的各知识点,Java中的多线程也就基本上掌握了.主要包括: Java线程具有五中基本状态 新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread(); 就绪状态(Runnable):当调用线程对象的

详细解读JAVA多线程实现的三种方式_java

最近在做代码优化时学习和研究了下JAVA多线程的使用,看了菜鸟们的见解后做了下总结. 1.继承Thread类实现多线程 继承Thread类的方法尽管被我列为一种多线程实现方式,但Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且,启动线程的唯一方法就是通过Thread类的start()实例方法.start()方法是一个native方法,它将启动一个新线程,并执行run()方法.这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run(

Java 多线程synchronized关键字详解(六)_java

synchronized 关键字,代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C. D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C .D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行.它包括两种用法:synchronized 方法和 synchronized 块. 多线程的同步机制对资源进行加锁,使得在同一个时间,只有一个线程可以进行操作,同步用以解决多

Java多线程编程之Lock用法实例_java

锁是控制多个线程对共享资源进行访问的工具.通常,锁提供了对共享资源的独占访问.一次只能有一个线程获得锁,对共享资源的所有访问都需要首先获得锁.不过,某些锁可能允许对共享资源并发访问,如 ReadWriteLock(维护了一对相关的锁,一个用于只读操作,另一个用于写入操作) 的读写锁. 1.Lock提供了无条件的.可轮询的.定时的.可中断的锁获取操作,所有加锁和解锁的方法都是显式的. public interface Lock{ void lock(); //加锁 //优先考虑响应中断,而不是响应