概念
单例模式(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()也并不需要加同步。