Java 单例模式

来源:互联网 发布:mac后台程序怎么关 编辑:程序博客网 时间:2024/06/05 11:58

概述

单例,为什么要使用单例?主要考虑以下两种情况

  • 实例对象比较占用资源,例如内存等,所以保持系统中只有一个实例对象,能有效的避免内存浪费,前提是你得业务逻辑允许你使用同一个对象实例。实际案例,JDBC 中数据库连接池。
  • 实例对象使用场景比较频繁,而且可以使用同一对象。如果你每次都去 New 一个出来,浪费内存是一个问题,同时也浪费了代码的执行时间。

今天拿出 Java 中常用的单例模式进行讲解,因为这个也是面试(校招类面试)经常叫你写的。

饿汉式单例

 public class HungrySingle {private static HungrySingle single =new HungrySingle() ;private HungrySingle(){    System.out.println("HungrySingle.HungrySingle ");}public static HungrySingle getInstance(){    System.out.println("HungrySingle.getInstance");    return single;}public static void main(String[] args) {    HungrySingle hungrySingle = getInstance();} }

这种单例利用静态变量初始化的特点,会在第一次调用这个类的时候,自动进行初始化。

打印结果

HungrySingle.HungrySingle HungrySingle.getInstance

由于使用的是静态初始化机制,所以不存在线程安全问题,但是这个单例叫做饿汉单例,也就是说,可能存在这种情况,单例被实例化了但是却没有被调用,会造成浪费。同时另一个问题就是,构造方法里面如果执行的初始化操作较多,或者代码操作比较耗时,这样的话,单例实例化执行的速度会受到影响。

懒汉式单例

懒汉式单例可以避免饿汉式单例的问题,能够保证在第一次使用的时候初始化对象

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

这种懒汉式不仅能避免浪费的问题,同时使用了 synchronized 关键字,所以是线程安全的。但是线程安全的牺牲代价就是,代码执行效率不高,因为每次只允许一个线程执行获取单例对象的代码,所以对多线程高效访问的场景基本不适用。
tip: synchroized 修饰的是 getInstance() 方法,在一个线程访问这个方法的时候,其他线程不能访问LazySingle 类的这个 getInstance() 方法,但是可以访问其他非 synchronized 修饰的方法。

double - check 的单例模式

还是从线程安全考虑,不过我们将 synchronized 的代码粒度变小,因为 synchronized 可以修饰方法,也可以修饰代码块,分别称为同步方法和同步代码块,同步代码块能够让我们更加有目的的对代码进行线程安全控制。下面的单例模式就是这种用法,称为 double - check 的单例模式。

public class SingleTon {private volatile static SingleTon singleTon;private SingleTon() {}public static SingleTon getInstance(){    if(singleTon == null)        synchronized (SingleTon.class)        {            if(singleTon == null){                singleTon = new SingleTon();            }        }    return singleTon;}}

这个代码是线程安全的,而且它的线程同步机制,仅仅是在单例对象没有被创建的时候有效,所以基本不会牺牲代码的执行性能。
这里我还使用了 volatile 关键字,这里 volatile 关键字是一种轻量级的 synchronized ,它在多处理器开发中保证了共享变量的可见性,也即是说,当一个线程修改了 volatile 共享变量之后,另一个线程读到的是修改后的值。

static 机制的单例

这种单例模式,例如了 static 机制,和内部类的特点

public class StaticSingle {private StaticSingle(){}public static StaticSingle getInstance(){    return SingleHolder.staticSingle;}private static class SingleHolder {    private static StaticSingle staticSingle = new StaticSingle();}}

这种单例是线程安全的,同时代码的执行性能基本是不受影响的,也能保证使用的时候,才会进行初始化。这种实现机制的意思是,参看下面的代码

 public class StaticSingle {static {new MyInnerClass(); }private StaticSingle(){    System.out.println("StaticSingle.StaticSingle");}public static StaticSingle getInstance(){    System.out.println("StaticSingle.getInstance");    return SingleHolder.staticSingle;}private static class SingleHolder {    private static StaticSingle staticSingle = new StaticSingle();    private SingleHolder(){        System.out.println("SingleHolder.SingleHolder");    }}public static class MyInnerClass{    public MyInnerClass(){        System.out.println("MyInnerClass.MyInnerClass");    }}public void foo(){    System.out.println("StaticSingle.foo");}public static void fooS(){    System.out.println("StaticSingle.fooS");}public static void main(String[] args) {    //fooS();    new StaticSingle().foo();}}

执行 main 方法中 的 foos() 方法打印如下

MyInnerClass.MyInnerClassStaticSingle.fooS

执行 main 方法中 new StaticSingle().foo() 打印如下

MyInnerClass.MyInnerClassStaticSingle.StaticSingleStaticSingle.foo

所以这个类第一次创建的时候,内部静态类并不会被创建,之前饿汉式的单例是,只要这个类被第一次访问了,就一定会执行单例对象的实例化操作。所以很好的避免了浪费问题。

关于单例对象被回收

单例对象基本是静态变量,在 Java 中对静态变量的回收是很谨慎的,所以基本可以认为不会主动被 GC 回收掉。

关于更深层次的线程安全问题

参考这篇文章

关于对象初始化延迟的问题。

0 0
原创粉丝点击