码迷,mamicode.com
首页 > 编程语言 > 详细

Java模式之单例模式

时间:2019-06-28 00:21:12      阅读:132      评论:0      收藏:0      [点我收藏+]

标签:cat   oid   第一个   饿汉式   ==   说明   单例对象   过程   返回   

单例模式是指某个类有唯一的实例
最常见的获取单例的方法有两种:饿汉式和懒汉式。

懒汉式单例模式:

public class Single1 {
    private static Single1 single1;
    private Single1(){}
    public static Single1 getInstance(){
    if (single1 == null){
    single1 = new Single1();
    }
    return single1;
  }
}

懒汉式单例模式,意思是调用getInstance()后才会生成对象,并返回对象,即需要的时候才会生成一个对象。
注意:getInstance()方法为static修饰的静态方法,使得方法属于类,通过[Single1.]便可调用getInstance(),避免其他类中创建Single1对象,另外private修饰的构造方法表明,只能在该类中生成对象。
懒汉式单例模式的特点是延迟加载,在需要该对象的时候才会生成对象,节省了不必要的空间;但该模式也存在一个致命的缺点,后面会讲到。

饿汉式单例模式:

public class Single2 {
  private static final Single2 SINGLE2 = new Single2();
  private Single2(){}
  public static Single2 getInstance(){
  return Single2.SINGLE2;
  }
}

饿汉式单例模式,意思是类加载就会立马生成一个对象,为了保证只能生成一个该类的对象,所以用关键字final修饰,即不可变。当需要该对象的时候,调用getInstance()方法即可返回该唯一的对象。
饿汉式单例模式的特点:饿汉式故名思议好比处于饥饿状态,类加载后立马生成该类的实例,不管需不需要;这容易造成空间的浪费,相比懒汉式单例,饿汉式单例不可用。

饿汉式单例模式还可以换一种方法来实现,即便不推荐使用;饿汉式静态模块单例模式:

public class Single3 {
  private Single3(){}
  private static Single3 single3;
  static
  {
  single3 = new Single3();
  }
  public static Single3 getInstance(){
  return single3;
  }
}
饿汉式静态模块单例模式的本质跟上面的饿汉式单例模式一样,只不过把直接生成单例的过程放到了静态代码块,即在类加载的过程中会执行静态代码块的内容,即生成了single3实例。
注意:这里的single3引用没有final关键字,因为一个类加载只会执行一次静态代码块,所以能确保single3的实例是唯一的。

上面说到懒汉式单例模式,即Single1有一个致命的缺点,我们通过代码来说明:

public class Client {
    static Single1 single1;
    public static void main(String[] args) {
    for (int i = 0; i < 10; i++){
    Runnable runnable = new Runnable(){
    public void run() {
    try {
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    e.printStackTrace();
  }
  single1 =Single1.getInstance();
  System.out.println(single1);
  }
  };
    new Thread(runnable).start();
    }
  }
}

代码含义:把懒汉式单例模式放到多线程下执行。
Thread.sleep(2000)是让当前执行单例模式的线程休眠2秒,让线程不那么快执行完,便于看到效果。
运行后的某一种结果:

com.design.single.Single1@34be8216
com.design.single.Single1@34be8216
com.design.single.Single1@34be8216
com.design.single.Single1@474e8d67
com.design.single.Single1@474e8d67
com.design.single.Single1@762589c3
com.design.single.Single1@474e8d67
com.design.single.Single1@474e8d67
com.design.single.Single1@34be8216
com.design.single.Single1@23194cf5

很明显,10个线程调用懒汉式单例模式后返回的对象居然有4个,这与单例模式的含义相违背,表明懒汉式单例模式在多线程下不可行,这是懒汉式单例模式致命的缺点。
分析执行过程可知,当线程1在执行if(single1 == null)后执行singe1 = new Single1()生成对象的同时,线程2也正好在判断if(single1 == null),由于线程1的对象还没有生成,所以线程2的判断为true,便进一步执行single1 = new Single1(),所以便会生成多个Single1对象。

为了解决这个问题,我们自然想到synchronized关键字,即在获取single1对象的方法getInstance()上加锁,使其成为

同步方法:

public class Single1 {
  private static Single1 single1;
  private Single1(){}
  public static synchronized Single1 getInstance(){
  if (single1 == null){
  single1 = new Single1();
  }
  return single1;
  }
}
同步方法getInstance()能保证每次只有一个线程进入方法内部,这样便能保证单例。
同步方法的特点:通过synchronized关键字使得线程获得Single1类的内置锁从而保证每次只有一个线程执行getInstance()。进一步分析执行过程:线程1执行方法getInstance时获取Single1的内置锁,此时线程2过来想要执行方法getInstance,发现方法所属类的锁已经被线程1占有,所以线程2被阻塞,接着线程3,线程4…继续被阻塞,这样导致的结果就是执行的效率非常低。我们想想,其实只有线程1进入方法getInstance生成实例后,方法2、3、4…都不需要生成实例,因此也就没必要阻塞后面的线程,所以可以缩小同步的范围,即把从同步方法变成同步代码块。

双重检查的单例模式:

public class Single1 {
    private static volatile Single1 single1;
    private Single1(){}
    public static Single1 getInstance(){
    if (single1 == null){
    synchronized(Single1.class){
    if (single1 == null){
    single1 = new Single1();
    }
  }
}
  return single1;
  }
}
双重检查的单例模式把同步从方法级别移到方法内部,只对必要的代码块进行同步;注意这里有两个判空,所以称为双重检查。第一次判空是所有线程都会执行的,当线程1判空后,就会获取Single1类的内置锁,线程1则势必会执行single1 = new Single1(),生成single1实例;假如线程1在执行同步代码块的时候,线程2进入方法getInstance,第一次判空为true,此时Single1的内置锁被线程1占有,因此被阻塞,当线程1执行完后退出同步代码块释放Single1的内置锁,线程2就会进入同步代码块,此时若线程3进来,因为线程1已经生成single1对象,所以线程3的第一次判空为false,则线程3执行返回对象,线程2进行第二次判空同样为false,直接返回single1实例。后面的线程在第一个判空处便为false。
双重检查单例模式的特点就是线程安全,延迟加载,效率高,比较常用。
注意:声明single1对象时,使用了volatile关键字。volatile关键字可以保证single1对象的可见性和有序性;这是防止指令重排从而保证single1对象的唯一。

静态内部类的单例模式:

public class Single4 {
  private Single4(){

  }
  private static class Single4Inner{
  private static final Single4 SINGLE4 = new Single4();
  }
  public static Single4 getInstance(){
  return Single4Inner.SINGLE4;
  }
}

静态内部类的单例模式与饿汉式单例模式有点类似,唯一的不同在于,饿汉式单例模式中单例对象是随着Single2类加载而生成,而静态内部类单例模式则通过静态内部类产生单例对象,其利用静态内部类不会随着外部类的加载而加载的特性使得当getInstance方法被调用后Single4Inner类才会被加载,从而生成SINGLE4对象,同时用static和final修饰,保证只会生成一个SINGLE4对象,保证了其线程的安全。

枚举类单例模式:

public enum Single5 {
  SINGLE_5;
  public Single5 getInstance() {
  return SINGLE_5;
  }
}
枚举类单例模式是最实用的一种单例模式。枚举类本身带有私有的构造方法,而每个枚举对象都是static和final修饰的对象,表明对象只能被实例化一次,所以在枚举实例的时候就会产生单例。

以上是所有产生单例的方法,总结有:饿汉式单例,懒汉式单例,饿汉式静态模块单例,同步方法单例,双重检查单例,静态内部类单例以及枚举类单例。

2 单例模式的原理
单例模式能够保证一个类仅有一个实例,并提供一个访问它的全局访问点。

3 单例模式的特点
故名思议,单例模式表明某个类只有一个实例。

Java模式之单例模式

标签:cat   oid   第一个   饿汉式   ==   说明   单例对象   过程   返回   

原文地址:https://www.cnblogs.com/fplblog/p/11100182.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!