设计模式之单例模式

概念

单例模式(Singleton),保证一个类仅有一个实例,并提供一访问它的全局访问点。

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止你实例化多个对象。一个最好的办法就是,让类自身负责保存它的唯一实例。这个类可以保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。

单例模式因为Singleton类封装它的唯一实例,这样它可以严格地控制客户怎样访问以及何时访问他。简单的说就是对唯一实例的受控访问。

单例模式的应用场景

  • 网站的计数器,一般也是采用单例模式实现,否则难以同步。
  • 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
  • Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
  • 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
  • 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。

单例模式有多种实现方式,如懒汉模式、饿汉模式。

1.饿汉模式

Singleton类,将构造函数私有化,不允许外界直接创建对象,只能通过public方法getInstance()获取。饿汉模式是线程安全的
public class Singleton {
 //1.将构造方法私有化,不允许外部直接创建对象
 private Singleton(){
 }
 //2 创建类的唯一实例,,使用 private static修饰
 private static Singleton instatnce=new Singleton();
 //3 提供一个用于获取实例的方法,使用public static修饰
 public static Singleton getInstance(){
  return instatnce;
 }
}
测试类Test
public class Test {
 public static void main(String[] args) {
  Singleton s1=Singleton.getInstance();
  Singleton s2=Singleton.getInstance();
  if(s1==s2){
   System.out.println("s1和s2是同一个实例");
  }else{
   System.out.println("s1和s2不是同一个实例");
  }
 }
}

2.懒汉模式

2.1 懒汉模式实现

Singleton2 类,懒汉模式没有考虑线程安全问题,它是线程不安全的,并发环境下很可能出现多个Singleton2 实例
public class Singleton2 {
 //1 将构造方法私有化,不允许外边直接创建对象
 private Singleton2(){
 }
 
 //2.创建类的唯一实例,用private static进行修饰,只进行声明,没有进行实例化
 private static Singleton2 instance;
 
 //3.提供一个用于获取实例的方法,使用public static修饰
 public static Singleton2 getInstance(){
  if(instance==null){
   instance=new Singleton2();
  }
  return instance;
 }
}
Test类
package com.imooc;
public class Test {
 public static void main(String[] args) {
  Singleton s1=Singleton.getInstance();
  Singleton s2=Singleton.getInstance();
  if(s1==s2){
   System.out.println("s1和s2是同一个实例");
  }else{
   System.out.println("s1和s2不是同一个实例");
  }
 
  Singleton2 s3=Singleton2.getInstance();
  Singleton2 s4=Singleton2.getInstance();
  if(s3==s4){
   System.out.println("s3和s4是同一个实例");
  }else{
   System.out.println("s3和s4不是同一个实例");
  }
 }
}

2.2 懒汉模式线程不安全的解决办法

2.2.1 给进程加锁处理

只要getInstance()加上同步锁,,一个线程必须等待另外一个线程创建完后才能使用这个方法,这就保证了单利的唯一性。
public class Singleton3 {
	//1 将构造方法私有化,不允许外边直接创建对象
	private Singleton3(){
	}
	//2.创建类的唯一实例,用private static进行修饰,只进行声明,没有进行实例化
	private static Singleton3 instance;
	//3.提供一个用于获取实例的方法,使用public static修饰
	//getInstance()加上同步锁,一个线程必须等待另外一个线程创建完后才能使用这个方法
	public synchronized static Singleton3 getInstance(){
		if(instance==null){
			instance=new Singleton3();
		}
		return instance;
	}
}

2.2.2 双重检查锁定

synchronized修饰的同步块要比一般代码要慢好几倍。在多次调用getInstance的情况下,性能会大大降低。现在去掉getInstance()的锁,加在if语句上。但是,这样的修改起不到任何作用:因为每次调用getInstance()的时候必然要经行同步,性能的问题还是存在。
public class Singleton3 {
	//1 将构造方法私有化,不允许外边直接创建对象
	private Singleton3(){
	}
	//2.创建类的唯一实例,用private static进行修饰,只进行声明,没有进行实例化
	private static Singleton3 instance;
	//3.提供一个用于获取实例的方法,使用public static修饰
	public  static Singleton3 getInstance(){
		synchronized (Singleton3.class) {
			if(instance==null){
				instance=new Singleton3();
			}
		}
		return instance;
	}
}
因此,我们事先判断一下是不是为null再去同步。首先判断instance是不是为null,如果为null在去进行同步,如果不为null,则直接返回instance对象。这就是double---checked----locking 设计实现单利模式
public class Singleton3 {
	//1 将构造方法私有化,不允许外边直接创建对象
	private Singleton3(){
	}
	//2.创建类的唯一实例,用private static进行修饰,只进行声明,没有进行实例化
	private static Singleton3 instance;
	//3.提供一个用于获取实例的方法,使用public static修饰
	public  static Singleton3 getInstance(){
		if(instance==null){
			synchronized (Singleton3.class) {
				if(instance==null){
					instance=new Singleton3();
				}
			}
		}
		return instance;
	}
}

2.2.3 静态内部类

使用Java的静态内部类
public class Singleton4 {
	private static class SingletonInstance{
		private static final Singleton4 instance=new Singleton4();
	}
	public Singleton4 getInstance(){
		return SingletonInstance.instance;
	}
	private Singleton4(){
	}
}

,因为Singleton4没有static的属性,因此并不会被初始化。直到调用getInstance()的时候,会首先加载SingletonInstance类,这个类有一个static的 Singleton4实例,因此需要调用 Singleton4的构造方法,然后getInstance()将把这个内部类的instance返回给使用者。由于这个instance是static的,因此并不会构造多次。
 
由于 SingletonInstance是私有静态内部类,所以不会被其他类知道,同样,static语义也要求不会有多个实例存在。并且,JSL规范定义,类的构造必须是原子性的,非并发的,因此不需要加同步块。同样,由于这个构造是并发的,所以getInstance()也并不需要加同步。









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值