Java单例模式详解

摘要:
回收站、任务管理器、线程池、缓存、日志对象、对话框、打印机和视频卡的驱动程序对象通常被设计为单个示例Singleton通过将构造方法限制为私有来防止类在外部实例化。Singleton的唯一实例只能通过getInstance()方法访问。通过Java反射机制,可以实例化构造方法为私有的类。

一、概念

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

二、五种单例模式的详解

  1、懒汉式单例

class LazySingleton{
    private static LazySingleton singleton;
    private LazySingleton(){
    }     public static LazySingleton getInstance(){         if(singleton==null){             singleton=new LazySingleton();         }         return singleton;     }   }

  Singleton通过将构造方法限定为private避免了类在外部被实例化,在同一个虚拟机范围内,Singleton的唯一实例只能通过getInstance()方法访问。(事实上,通过Java反射机制是能够实例化构造方法为private的类的,那基本上会使所有的Java单例实现失效。此问题在此处不做讨论,姑且掩耳盗铃地认为反射机制不存在。)

  但是以上实现没有考虑线程安全问题。所谓线程安全是指:如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。或者说:一个类或者程序所提供的接口对于线程来说是原子操作或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题。显然以上实现并不满足线程安全的要求,在并发环境下很可能出现多个Singleton实例。

  我们可以对懒汉式进行改进,如下所示:

public class Singleton {
    private static Singleton instance=null;
    private Singleton(){
        
    }
    public static synchronized Singleton getInstance(){
        if(instance==null){
            instance=new Singleton();
        }
        return instance;
    }
}

  在懒汉式的基础上加上了同步锁,使得在多线程的情况下可以用。例如:当两个线程同时想创建实例,由于在一个时刻只有一个线程能得到同步锁,当第一个线程加上锁以后,第二个线程只能等待。第一个线程发现实例没有创建,创建之。第一个线程释放同步锁,第二个线程才可以加上同步锁,执行下面的代码。由于第一个线程已经创建了实例,所以第二个线程不需要创建实例。保证在多线程的环境下也只有一个实例。
缺点:每次通过getInstance方法得到singleton实例的时候都有一个试图去获取同步锁的过程。而众所周知,加锁是很耗时的。能避免则避免。

  2、饿汉式单例(建议使用)

class HungrySingleton{
    private static HungrySingleton singleton=new HungrySingleton();
    private HungrySingleton(){}
    public static HungrySingleton getInstance(){
        return singleton;
    }
}

  初试化静态的singleton创建一次。如果我们在Singleton类里面写一个静态的方法不需要创建实例,它仍然会早早的创建一次实例。而降低内存的使用率。

缺点:没有lazy loading的效果,从而降低内存的使用率。

  3、静态内部类单例

class InternalSingleton{
    private static class SingletonHolder{
        private final static  InternalSingleton INSTANCE=new InternalSingleton();
    }   
    private InternalSingleton(){}
    public static InternalSingleton getInstance(){
        return SingletonHolder.INSTANCE;
    }
}

  定义一个私有的内部类,在第一次用这个嵌套类时,会创建一个实例。而类型为SingletonHolder的类,只有在Singleton.getInstance()中调用,由于私有的属性,他人无法使用SingleHolder,不调用Singleton.getInstance()就不会创建实例。

优点:加载时不会初始化静态变量INSTANCE,因为没有主动使用,达到Lazy loading

  4.枚举单例

enum EnumSingleton{
    INSTANCE;
    public void doSomeThing(){
    }
}

  《Effective Java》作者推荐使用的方法,优点:不仅能避免多线程同步问题,而且还能防止反序列化重新创建新的对象

  五、 双重校验锁单例 ,前后两次判断实例是否存在

class LockSingleton{
    private volatile static LockSingleton singleton;
    private LockSingleton(){}
     
    //详见:http://www.ibm.com/developerworks/cn/java/j-dcl.html
    public static LockSingleton getInstance(){
        if(singleton==null){
            synchronized(LockSingleton.class){
                if(singleton==null){
                    singleton=new LockSingleton();
                }
            }
        }
        return singleton;
    }
     
}

  只有当singleton为null时,需要获取同步锁,创建一次实例。当实例被创建,则无需试图加锁。
缺点:用双重if判断,复杂,容易出错。

免责声明:文章转载自《Java单例模式详解》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Ubuntu12.04(X86_64)上安装Mesa8.0.4xlua 原理下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Django项目常见面试问题

1.python中的lambda是什么意思,可以举例 1 匿名函数 2 a = lambda x:x+1 3 print(a(1)) 2.请写出以下代码执行的结果 1 class Parent(object): 2 x = 1 3 class Child1(Parent): 4 pass 5 class Child2(Parent): 6 pa...

UI线程异常处理方法

当应用程序启动,创建了一个叫“main”的线程,用于管理UI相关,又叫UI线程。其他线程叫工作线程(Work Thread)。 Single Thread Model   一个组件的创建并不会新建一个线程,他们的创建都在UI线程中进行,包括他们的回调方法,如onKeyDown()。 当在UI线程中进行某些耗时的操作时,将会阻塞UI线程,一般阻塞超过5秒就...

java进程占用CPU高的问题

一. 上节回顾怎么查看CPU使用率? top:显示了系统总体的CPU和内存使用情况,以及各个进程的资源使用情况。默认每隔3s刷新一次 ps:只显示每个进程的资源使用情况 top并没有细分进程的用户态CPU和内核态CPU pidstat:可以分析每个进程的CPU使用情况 通过top,ps,pidstat这些工具,能够很快找到CPU使用率较高的进程。要知道CP...

MySQL主从复制的实现过程

一、什么是主从复制   将主数据库中的DDL和DML操作通过二进制日志传输到从数据库上,然后将这些日志重新执行(重做);从而使得从数据库的数据与主数据库保持一致。 基本原理:   MySQL支持单向、异步复制,复制过程中一个服务器充当主服务器,而一个或多个其它服务器充当从服务器。   MySQL复制是基于主服务器在二进制日志中跟踪所有对数据库的更改。因此,...

c#FileStream文件读写

//C#文件流写文件,默认追加FileMode.Append             string msg = "okffffffffffffffff";            byte[] myByte = System.Text.Encoding.UTF8.GetBytes(msg);            using (FileStream fsWr...

Jmeter属性和变量

一、Jmeter中的属性: 1、JMeter属性统一定义在jmeter.properties文件中,我们可以在该文件中添加自定义的属性 2、JMeter属性在测试脚本的任何地方都是可见的(全局),通常被用来定义一些JMeter使用的默认值,可以用于在线程间传递信息。 3、JMeter属性可以在测试计划中通过函数 _P 进行引用,但是不能作为特定线程的变量值...