JAVA设计模式笔记整理(三)

本文详细解析单件模式的核心概念,强调确保一个类只有一个实例的重要性,并探讨了在Java环境下通过私有构造器、静态方法和静态变量实现单件模式的基本结构。同时,文章深入分析了多线程环境下单件模式面临的问题,提出了同步方法、急切实例化和双重检查加锁等解决方案,旨在提升实例化效率和避免并发问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

5.单件模式

定义:

确保一个类只有一个实例,并提供一个全局访问点。

有一些对象其实我们只需要一个,比方说线程池(threadpool)、缓存(cache)、对话框、处理偏好设置和注册表的对象、日志对象……如果这类对象制造出多个实例,就会导致许多问题产生。

虽然利用全局变量也可以确保只有一个实例会被创建,但这必须在程序一开始就创建好对象(跟JVM具体实现有关)。利用单件模式,我们可以在需要时才创建对象。


单件模式的类图:


一个经典的单件模式:

public class Singleton {
	private static Singleton uniqueInstance;
	private Singleton(){}
	public static Singleton getInstance(){
		if(uniqueInstance==null){
			uniqueInstance=new Singleton();
		}
		return uniqueInstance;
	}
	//这里是其他的有用方法
}
在getInstance()方法中,if语句的作用是,如果uniqueInstance是空的,表示还没有创建实例,此时我们就利用私有的构造器产生一个Singleton实例并把它赋值到uniqueInstance静态变量中。如果我们不需要这个实例,它就永远不会产生,这就是“延迟实例化”(lazy instantiaze)。如果uniqueInstance不是null,就表示之前已经创建过对象。我们就直接跳到return语句。

*这样的实现会有一些问题

示例代码:

public class Singleton {
	private static Singleton uniqueInstance;
	private long id;
	private Singleton(long id){
		this.id=id;
	}
	public static Singleton getInstance(){
		if(uniqueInstance==null){
			uniqueInstance=new Singleton(Thread.currentThread().getId());
		}
		return uniqueInstance;
	}
	public void getId(){
		System.out.println("The singletonis id is "+id);
	}
	//这里是其他的有用方法
}
public class SingletonRun implements Runnable {
	public void run(){
		Singleton.getInstance().getId();
	}
}
public class TestSingleton {
	public static void main(String[] args){
		Thread t1=new Thread(new SingletonRun());
		Thread t2=new Thread(new SingletonRun());
		t1.start();
		t2.start();
	}
}
所期望的的结果应类似:
The singletonis id is 8
The singletonis id is 8
但往往会出现类似如下的情况:
The singletonis id is 9
The singletonis id is 8

出现这样的情况是因为

一开始 线程t1和线程t2的uniqueInstance变量为null

此时当线程t1执行到

if(uniqueInstance==null)
时,if条件刚判断结束进入if后面的代码块后,操作系统中断了t1线程的执行, 另一线程t2开始被调度。此时若t2也进入了if的语句块,并完整执行了语句块的内容,即
uniqueInstance=new Singleton(Thread.currentThread().getId());

并return后,线程t2的getId()方法返回的便是t2的id号,

之后操作系统继续调度t1执行,t1也完整执行了if语句块中的内容,即

uniqueInstance=new Singleton(Thread.currentThread().getId());
并return后,线程t1 的getId()方法返回的便是t1的id号。(若t2同t1一样if条件判断结束后便被中断,t1完整执行了if语句块内容并return后,也会出现错误情况。)


如何改善多线程:

1) 如果getInstance()的性能对应用程序不是很关键,可以将getInstance()生命为同步方法

public static synchronized Singleton getInstance(){
		if(uniqueInstance==null){
			uniqueInstance=new Singleton(Thread.currentThread().getId());
		}
		return uniqueInstance;
	}
但是你必须知道,同步一个方法可能造成程序执行效率下降100倍。如果程序中频繁用到getInstance(),你就得重新考虑了。

2)使用“急切”创建实例,而不用延迟实例化的做法

public class Singleton{
	private static Singleton uniqueInstance=new Singleton();
	private Singleton(){}
	public static Singleton getInstance(){
		return uniqueInstance;
	}
}
利用这个做法,我们依赖JVM在加载这个类时马上创建此唯一的单件实例。JVM保证在任何线程访问uniqueInstance静态变量之前,一定先创建此实例。

3)用“双重检查加锁”,在getInstance()中减少使用同步

public class Singleton{
	private volatile static Singleton uniqueInstance;
	private Singleton(){}
	public static Singleton getInstance(){
		if(uniqueInstance ==null){
			synchronized(Singleton.class){
				if(uniqueInstance==null){
					uniqueInstance=new Singleton();
				}
			}
		}
		return uniqueInstance;
	}
}
如果性能是你关心的的重点,那么这个做法可以帮你大大地减少getInstance()的时间耗费。

*在1.4及更早版本的Java中,许多关于JVM对于volatile关键字的实现会导致双重检查加锁的失效

*关于volatile关键字,可以看这里


要点:

单件模式确保程序中一个类最多只有一个实例

单件模式也提供访问这个实例的全局点

在Java中实现单件模式需要私有的构造器、一个静态方法和一个静态变量

确定在性能和资源上的限制,然后小心的选择适当的方案来实现单件,以解决多线程的问题

如果不是采用jdk1.5,双重检查加锁实现会失效

小心,你如果使用多个类加载器,可能导致单件失效而产生多个实例(解决办法,自行指定类加载器,并指定同一个类加载器)


自说自话:原来单件并不像想象的那么简单,本以为

public class Singleton {
	private static Singleton uniqueInstance;
	private Singleton(){}
	public static Singleton getInstance(){
		if(uniqueInstance==null){
			uniqueInstance=new Singleton();
		}
		return uniqueInstance;
	}
	//这里是其他的有用方法
}
就是单件模式了,没想到还要考虑多线程的问题。早点知道ocjp这题也就不会错了,呵呵。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值