- 浏览: 80015 次
- 性别:
- 来自: 上海
-
文章分类
最新评论
java单例模式的线程安全
推荐使用:
多线程安全单例模式实例三(使用双重同步锁)
可以说单例模式是所有设计模式中最简单的一种。
单例模式就是说系统中对于某类的只能有一个对象,不可能出来第二个。
单例模式也是23中设计模式中在面试时少数几个会要求写代码的模式之一。主要考察的是多线程下面单例模式的线程安全性问题。
1.多线程安全单例模式实例一(不使用同步锁)
1 public class Singleton { 2 private static Singleton sin=new Singleton(); ///直接初始化一个实例对象 3 private Singleton(){ ///private类型的构造函数,保证其他类对象不能直接new一个该对象的实例 4 } 5 public static Singleton getSin(){ ///该类唯一的一个public方法 6 return sin; 7 } 8 }
上述代码中的一个缺点是该类加载的时候就会直接new 一个静态对象出来,当系统中这样的类较多时,会使得启动速度变慢 。现在流行的设计都是讲“延迟加载”,我们可以在第一次使用的时候才初始化第一个该类对象。所以这种适合在小系统。
2.多线程安全单例模式实例二(使用同步方法)
1 public class Singleton { 2 private static Singleton instance; 3 private Singleton (){ 4 5 } 6 public static synchronized Singleton getInstance(){ //对获取实例的方法进行同步 7 if (instance == null) 8 instance = new Singleton(); 9 return instance; 10 } 11 }
上述代码中的一次锁住了一个方法, 这个粒度有点大 ,改进就是只锁住其中的new语句就OK。就是所谓的“双重锁”机制。
3.多线程安全单例模式实例三(使用双重同步锁)
1 public class Singleton { 2 private static Singleton instance; 3 private Singleton (){ 4 } 5 public static Singleton getInstance(){ //对获取实例的方法进行同步 6 if (instance == null){ 7 synchronized(Singleton.class){ 8 if (instance == null) 9 instance = new Singleton(); 10 } 11 } 12 return instance; 13 } 14 15 }
以下是详细分析:
在所有的设计模式中,单例模式是我们在项目开发中最为常见的设计模式之一,而单例模式有很多种实现方式,你是否都了解呢?高并发下如何保证单例模式的线程安全性呢?如何保证序列化后的单例对象在反序列化后任然是单例的呢?这些问题在看了本文之后都会一一的告诉你答案,赶快来阅读吧!
什么是单例模式?
在文章开始之前我们还是有必要介绍一下什么是单例模式。单例模式是为确保一个类只有一个实例,并为整个系统提供一个全局访问点的一种模式方法。
从概念中体现出了单例的一些特点:
(1)、在任何情况下,单例类永远只有一个实例存在
(2)、单例需要有能力为整个系统提供这一唯一实例
为了便于读者更好的理解这些概念,下面给出这么一段内容叙述:
在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
正是由于这个特点,单例对象通常作为程序中的存放配置信息的载体,因为它能保证其他对象读到一致的信息。例如在某个服务器程序中,该服务器的配置信息可能存放在数据库或文件中,这些配置数据由某个单例对象统一读取,服务进程中的其他对象如果要获取这些配置信息,只需访问该单例对象即可。这种方式极大地简化了在复杂环境
下,尤其是多线程环境下的配置管理,但是随着应用场景的不同,也可能带来一些同步问题。
各式各样的单例实现
温馨提示:本文叙述中涉及到的相关源码可以在这里进行下载源码,读者可免积分下载。
1、饿汉式单例
饿汉式单例是指在方法调用前,实例就已经创建好了。下面是实现代码:
- packageorg.mlinge.s01;
- publicclassMySingleton{
- privatestaticMySingletoninstance=newMySingleton();
- privateMySingleton(){}
- publicstaticMySingletongetInstance(){
- returninstance;
- }
- }
- packageorg.mlinge.s01;
- publicclassMyThreadextendsThread{
- @Override
- publicvoidrun(){
- System.out.println(MySingleton.getInstance().hashCode());
- }
- publicstaticvoidmain(String[]args){
- MyThread[]mts=newMyThread[10];
- for(inti=0;i<mts.length;i++){
- mts[i]=newMyThread();
- }
- for(intj=0;j<mts.length;j++){
- mts[j].start();
- }
- }
- }
以上代码运行结果:
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
从运行结果可以看出实例变量额hashCode值一致,这说明对象是同一个,饿汉式单例实现了。
2、懒汉式单例
懒汉式单例是指在方法调用获取实例时才创建实例,因为相对饿汉式显得“不急迫”,所以被叫做“懒汉模式”。下面是实现代码:
- packageorg.mlinge.s02;
- publicclassMySingleton{
- privatestaticMySingletoninstance=null;
- privateMySingleton(){}
- publicstaticMySingletongetInstance(){
- if(instance==null){//懒汉式
- instance=newMySingleton();
- }
- returninstance;
- }
- }
- packageorg.mlinge.s02;
- publicclassMySingleton{
- privatestaticMySingletoninstance=null;
- privateMySingleton(){}
- publicstaticMySingletongetInstance(){
- try{
- if(instance!=null){//懒汉式
- }else{
- //创建实例之前可能会有一些准备性的耗时工作
- Thread.sleep(300);
- instance=newMySingleton();
- }
- }catch(InterruptedExceptione){
- e.printStackTrace();
- }
- returninstance;
- }
- }
- packageorg.mlinge.s02;
- publicclassMyThreadextendsThread{
- @Override
- publicvoidrun(){
- System.out.println(MySingleton.getInstance().hashCode());
- }
- publicstaticvoidmain(String[]args){
- MyThread[]mts=newMyThread[10];
- for(inti=0;i<mts.length;i++){
- mts[i]=newMyThread();
- }
- for(intj=0;j<mts.length;j++){
- mts[j].start();
- }
- }
- }
执行结果如下:
- 1210420568
- 1210420568
- 1935123450
- 1718900954
- 1481297610
- 1863264879
- 369539795
- 1210420568
- 1210420568
- 602269801
从这里执行结果可以看出,单例的线程安全性并没有得到保证,那要怎么解决呢?
3、线程安全的懒汉式单例
要保证线程安全,我们就得需要使用同步锁机制,下面就来看看我们如何一步步的解决 存在线程安全问题的懒汉式单例(错误的单例)。
(1)、 方法中声明synchronized关键字
出现非线程安全问题,是由于多个线程可以同时进入getInstance()方法,那么只需要对该方法进行synchronized的锁同步即可:
- packageorg.mlinge.s03;
- publicclassMySingleton{
- privatestaticMySingletoninstance=null;
- privateMySingleton(){}
- publicsynchronizedstaticMySingletongetInstance(){
- try{
- if(instance!=null){//懒汉式
- }else{
- //创建实例之前可能会有一些准备性的耗时工作
- Thread.sleep(300);
- instance=newMySingleton();
- }
- }catch(InterruptedExceptione){
- e.printStackTrace();
- }
- returninstance;
- }
- }
此时任然使用前面验证多线程下执行情况的MyThread类来进行验证,将其放入到org.mlinge.s03包下运行,执行结果如下:
- 1689058373
- 1689058373
- 1689058373
- 1689058373
- 1689058373
- 1689058373
- 1689058373
- 1689058373
- 1689058373
- 1689058373
从执行结果上来看,问题已经解决了,但是这种实现方式的运行效率会很低。同步方法效率低,那我们考虑使用同步代码块来实现:
(2)、 同步代码块实现
- packageorg.mlinge.s03;
- publicclassMySingleton{
- privatestaticMySingletoninstance=null;
- privateMySingleton(){}
- //publicsynchronizedstaticMySingletongetInstance(){
- publicstaticMySingletongetInstance(){
- try{
- synchronized(MySingleton.class){
- if(instance!=null){//懒汉式
- }else{
- //创建实例之前可能会有一些准备性的耗时工作
- Thread.sleep(300);
- instance=newMySingleton();
- }
- }
- }catch(InterruptedExceptione){
- e.printStackTrace();
- }
- returninstance;
- }
- }
(3)、 针对某些重要的代码来进行单独的同步(可能非线程安全)
针对某些重要的代码进行单独的同步,而不是全部进行同步,可以极大的提高执行效率,我们来看一下:
- packageorg.mlinge.s04;
- publicclassMySingleton{
- privatestaticMySingletoninstance=null;
- privateMySingleton(){}
- publicstaticMySingletongetInstance(){
- try{
- if(instance!=null){//懒汉式
- }else{
- //创建实例之前可能会有一些准备性的耗时工作
- Thread.sleep(300);
- synchronized(MySingleton.class){
- instance=newMySingleton();
- }
- }
- }catch(InterruptedExceptione){
- e.printStackTrace();
- }
- returninstance;
- }
- }
- 1481297610
- 397630378
- 1863264879
- 1210420568
- 1935123450
- 369539795
- 590202901
- 1718900954
- 1689058373
- 602269801
(4)、Double Check Locking双检查锁机制(推荐)
为了达到线程安全,又能提高代码执行效率,我们这里可以采用DCL的双检查锁机制来完成,代码实现如下:
- packageorg.mlinge.s05;
- publicclassMySingleton{
- //使用volatile关键字保其可见性
- volatileprivatestaticMySingletoninstance=null;
- privateMySingleton(){}
- publicstaticMySingletongetInstance(){
- try{
- if(instance!=null){//懒汉式
- }else{
- //创建实例之前可能会有一些准备性的耗时工作
- Thread.sleep(300);
- synchronized(MySingleton.class){
- if(instance==null){//二次检查
- instance=newMySingleton();
- }
- }
- }
- }catch(InterruptedExceptione){
- e.printStackTrace();
- }
- returninstance;
- }
- }
- 369539795
- 369539795
- 369539795
- 369539795
- 369539795
- 369539795
- 369539795
- 369539795
- 369539795
- 369539795
这里在声明变量时使用了volatile关键字来保证其线程间的可见性;在同步代码块中使用二次检查,以保证其不被重复实例化。集合其二者,这种实现方式既保证了其高效性,也保证了其线程安全性。
4、使用静态内置类实现单例模式
DCL解决了多线程并发下的线程安全问题,其实使用其他方式也可以达到同样的效果,代码实现如下:
- packageorg.mlinge.s06;
- publicclassMySingleton{
- //内部类
- privatestaticclassMySingletonHandler{
- privatestaticMySingletoninstance=newMySingleton();
- }
- privateMySingleton(){}
- publicstaticMySingletongetInstance(){
- returnMySingletonHandler.instance;
- }
- }
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
5、序列化与反序列化的单例模式实现
静态内部类虽然保证了单例在多线程并发下的线程安全性,但是在遇到序列化对象时,默认的方式运行得到的结果就是多例的。
代码实现如下:
- packageorg.mlinge.s07;
- importjava.io.Serializable;
- publicclassMySingletonimplementsSerializable{
- privatestaticfinallongserialVersionUID=1L;
- //内部类
- privatestaticclassMySingletonHandler{
- privatestaticMySingletoninstance=newMySingleton();
- }
- privateMySingleton(){}
- publicstaticMySingletongetInstance(){
- returnMySingletonHandler.instance;
- }
- }
- packageorg.mlinge.s07;
- importjava.io.File;
- importjava.io.FileInputStream;
- importjava.io.FileNotFoundException;
- importjava.io.FileOutputStream;
- importjava.io.IOException;
- importjava.io.ObjectInputStream;
- importjava.io.ObjectOutputStream;
- publicclassSaveAndReadForSingleton{
- publicstaticvoidmain(String[]args){
- MySingletonsingleton=MySingleton.getInstance();
- Filefile=newFile("MySingleton.txt");
- try{
- FileOutputStreamfos=newFileOutputStream(file);
- ObjectOutputStreamoos=newObjectOutputStream(fos);
- oos.writeObject(singleton);
- fos.close();
- oos.close();
- System.out.println(singleton.hashCode());
- }catch(FileNotFoundExceptione){
- e.printStackTrace();
- }catch(IOExceptione){
- e.printStackTrace();
- }
- try{
- FileInputStreamfis=newFileInputStream(file);
- ObjectInputStreamois=newObjectInputStream(fis);
- MySingletonrSingleton=(MySingleton)ois.readObject();
- fis.close();
- ois.close();
- System.out.println(rSingleton.hashCode());
- }catch(FileNotFoundExceptione){
- e.printStackTrace();
- }catch(IOExceptione){
- e.printStackTrace();
- }catch(ClassNotFoundExceptione){
- e.printStackTrace();
- }
- }
- }
- 865113938
- 1442407170
解决办法就是在反序列化的过程中使用readResolve()方法,单例实现的代码如下:
- packageorg.mlinge.s07;
- importjava.io.ObjectStreamException;
- importjava.io.Serializable;
- publicclassMySingletonimplementsSerializable{
- privatestaticfinallongserialVersionUID=1L;
- //内部类
- privatestaticclassMySingletonHandler{
- privatestaticMySingletoninstance=newMySingleton();
- }
- privateMySingleton(){}
- publicstaticMySingletongetInstance(){
- returnMySingletonHandler.instance;
- }
- //该方法在反序列化时会被调用,该方法不是接口定义的方法,有点儿约定俗成的感觉
- protectedObjectreadResolve()throwsObjectStreamException{
- System.out.println("调用了readResolve方法!");
- returnMySingletonHandler.instance;
- }
- }
- 865113938
- 调用了readResolve方法!
- 865113938
6、使用static代码块实现单例
静态代码块中的代码在使用类的时候就已经执行了,所以可以应用静态代码块的这个特性的实现单例设计模式。
- packageorg.mlinge.s08;
- publicclassMySingleton{
- privatestaticMySingletoninstance=null;
- privateMySingleton(){}
- static{
- instance=newMySingleton();
- }
- publicstaticMySingletongetInstance(){
- returninstance;
- }
- }
- packageorg.mlinge.s08;
- publicclassMyThreadextendsThread{
- @Override
- publicvoidrun(){
- for(inti=0;i<5;i++){
- System.out.println(MySingleton.getInstance().hashCode());
- }
- }
- publicstaticvoidmain(String[]args){
- MyThread[]mts=newMyThread[3];
- for(inti=0;i<mts.length;i++){
- mts[i]=newMyThread();
- }
- for(intj=0;j<mts.length;j++){
- mts[j].start();
- }
- }
- }
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
- 1718900954
7、使用枚举数据类型实现单例模式
枚举enum和静态代码块的特性相似,在使用枚举时,构造方法会被自动调用,利用这一特性也可以实现单例:
- packageorg.mlinge.s09;
- publicenumEnumFactory{
- singletonFactory;
- privateMySingletoninstance;
- privateEnumFactory(){//枚举类的构造方法在类加载是被实例化
- instance=newMySingleton();
- }
- publicMySingletongetInstance(){
- returninstance;
- }
- }
- classMySingleton{//需要获实现单例的类,比如数据库连接Connection
- publicMySingleton(){}
- }
- packageorg.mlinge.s09;
- publicclassMyThreadextendsThread{
- @Override
- publicvoidrun(){
- System.out.println(EnumFactory.singletonFactory.getInstance().hashCode());
- }
- publicstaticvoidmain(String[]args){
- MyThread[]mts=newMyThread[10];
- for(inti=0;i<mts.length;i++){
- mts[i]=newMyThread();
- }
- for(intj=0;j<mts.length;j++){
- mts[j].start();
- }
- }
- }
- 1481297610
- 1481297610
- 1481297610
- 1481297610
- 1481297610
- 1481297610
- 1481297610
- 1481297610
- 1481297610
- 1481297610
8、完善使用enum枚举实现单例模式
不暴露枚举类实现细节的封装代码如下:
- packageorg.mlinge.s10;
- publicclassClassFactory{
- privateenumMyEnumSingleton{
- singletonFactory;
- privateMySingletoninstance;
- privateMyEnumSingleton(){//枚举类的构造方法在类加载是被实例化
- instance=newMySingleton();
- }
- publicMySingletongetInstance(){
- returninstance;
- }
- }
- publicstaticMySingletongetInstance(){
- returnMyEnumSingleton.singletonFactory.getInstance();
- }
- }
- classMySingleton{//需要获实现单例的类,比如数据库连接Connection
- publicMySingleton(){}
- }
- packageorg.mlinge.s10;
- publicclassMyThreadextendsThread{
- @Override
- publicvoidrun(){
- System.out.println(ClassFactory.getInstance().hashCode());
- }
- publicstaticvoidmain(String[]args){
- MyThread[]mts=newMyThread[10];
- for(inti=0;i<mts.length;i++){
- mts[i]=newMyThread();
- }
- for(intj=0;j<mts.length;j++){
- mts[j].start();
- }
- }
- }
- 1935123450
- 1935123450
- 1935123450
- 1935123450
- 1935123450
- 1935123450
- 1935123450
- 1935123450
- 1935123450
- 1935123450
以上就是本文要介绍的所有单例模式的实现,相信认真阅读的读者都已经明白文章开头所引入的那几个问题了,祝大家读得开心:-D!
备注:本文的编写思路和实例源码参照《Java多线程编程核心技术》-(高洪岩)一书中第六章的学习案例撰写。
相关推荐
Java 单例模式线程安全问题详解 Java 单例模式线程安全问题是指在 Java 中实现单例模式时,如何确保线程安全的问题。单例模式是指在整个应用程序生命周期中,只有一个实例存在的设计模式。这种模式可以提高性能,...
Java中懒汉单例设计模式线程安全测试,单例设计模式的测试
在Java中,有多种实现单例模式的方法,每种都有其特点和适用场景。接下来,我们将深入探讨这些实现方式。 首先,我们来看**懒汉式(Lazy Initialization)**。这种实现方式是在类被首次请求时才创建单例对象,延迟...
- **线程安全问题**:懒汉式单例模式在多线程环境下可能会导致创建多个实例,因此需要采用同步机制保证线程安全,例如使用`synchronized`关键字。 - **静态内部类方式** - **实现**: ```java class Single3 {...
Java中的单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供全局访问点。在Java编程中,单例模式常用于控制资源的访问,比如数据库连接池、线程池或者日志对象等。本篇文章将深入探讨如何在Java中...
总之,Java单例模式有多种实现方式,每种方式都有其适用场景和优缺点。选择哪种实现方式取决于项目需求,如是否需要延迟加载、是否考虑多线程环境、代码的可读性和维护性等。在实际开发中,应根据具体情况灵活选择。
### 线程安全的单例模式详解 #### 一、单例模式简介 单例模式(Singleton Pattern)是软件开发中最常用的创建型设计模式之一,它的主要目标是确保一个类只有一个实例,并提供一个全局访问点。单例模式在很多场景下...
### 使用Java单例模式实现一个简单的日志记录器 #### 一、单例模式简介 单例模式是一种常用的软件设计模式,在该模式中,一个类只能创建一个实例,并且提供了一个全局访问点来访问该实例。单例模式的主要优点包括...
Java 单例模式 懒汉模式 //懒汉式 多线程中不可以保证是一个对象
以上就是单例模式在Java中的常见实现方式,其中线程安全的实现包括饿汉式、静态代码块、双重检查锁定、静态内部类以及枚举方式。在实际应用中,应根据项目需求选择适合的单例实现方式,考虑到性能和线程安全等因素。...
Java单例模式是一种常用的设计模式,它保证一个类只有一个实例,并提供全局访问点。这种模式在需要频繁创建和销毁对象的场景中,或者当对象昂贵时(如数据库连接),能够节省系统资源,提高效率。本篇文章将深入探讨...
Java单例模式是一种设计模式,它允许在程序中创建唯一一个类实例,通常用于管理共享资源,例如数据库连接、线程池或者配置对象等。单例模式的核心在于限制类的构造函数,确保类只能被初始化一次,从而实现全局唯一的...
Java中的单例模式主要分为三种实现方式:懒汉式单例、饿汉式单例和登记式单例。 1. 懒汉式单例(Lazy Initialization) 懒汉式单例的特点是在类被加载时不创建实例,而是在首次调用`getInstance()`方法时才进行实例...
### JAVA单例模式的几种实现方法 #### 一、饿汉式单例类 饿汉式单例类是在类初始化时就已经完成了实例化的操作。这种实现方式简单且线程安全,因为实例化过程是在编译期间完成的,不会受到多线程的影响。 **代码...
在Java编程中,单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。这种模式在需要频繁创建和销毁对象的场景中尤其有用,因为它可以节省系统资源并确保对象间的协调一致。以下是...
### Java单例模式应用研究 #### 一、单例模式概述 单例模式(Singleton Pattern)作为一种最基本的创建型设计模式,其主要目的是控制一个类的实例化过程,确保在整个应用程序中仅存在一个实例,并且该实例能够被全局...
Java单例模式是一种常见的设计模式,它在软件工程中用于控制类的实例化过程,确保一个类只有一个实例,并提供一个全局访问点。这种模式在系统资源管理、缓存、日志记录等方面应用广泛。下面我们将深入探讨Java单例...
Java单例模式是一种设计模式,它保证一个类只有一个实例,并提供一个全局访问点。在数据库连接管理中,使用单例模式能有效控制资源,避免频繁创建和关闭数据库连接导致的性能损失和资源浪费。以下是对Java单例模式...
在Java中实现单例模式有多种方法: 1. **饿汉式(静态常量)**:在类加载时就完成了初始化,所以没有线程安全问题,但这种实现方式无法实现延迟加载。 ```java public class Singleton { private static final ...