1.什么是单例模式?
确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一实例。
这个其实很好理解,我们windows的任务管理器其实就是一个单例。
2.单例模式的三个要点:
(1)某个类只能有一个实例
(2)它必须自行创建这个实例
(3)它必须自行向整个系统提供这个实例
3.单例模式结构
单例模式只包含一个类,即单例类。
单例模式只包含一个单例角色,也就是Singleton。在内部创建唯一实例,并通过静态方法getInstance()让客户的使用它。为了防止外部对单例实例化,构造函数用private。
(1)单例类
public class Singleton {
private static Singleton instance = null;//静态私有成员变量
//私有构造函数
private Singleton(){
}
//静态共有工厂方法,返回唯一实例
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
(2)测试
public class Client {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("两个对象是相同实例");
}else{
System.out.println("两个对象不是相同实例");
}
}
}
(3)测试结果
4.单例模式应用实例——保证负载均衡器的唯一性
(1)单例类
public class LoadBalancer {
//私有静态成员变量,存储唯一实例
private static LoadBalancer instance = null;
//服务器集合
private List serverList = null;
//私有构造函数
private LoadBalancer(){
serverList = new ArrayList();
}
//公有静态成员方法,返回唯一实例
public static LoadBalancer getLoadBalancer(){
if(instance == null){
instance = new LoadBalancer();
}
return instance;
}
//增加服务器
public void addServer(String server){
serverList.add(server);
}
//删除服务器
public void removeServer(String server){
serverList.remove(server);
}
//使用Random类随机获取服务器
public String getServer(){
Random random = new Random();
int i = random.nextInt(serverList.size());
return (String)serverList.get(i);
}
}
(2)测试类
public class Client {
public static void main(String[] args) {
//创建4个LoadBalancer对象
LoadBalancer balancer1,balancer2,balancer3,balancer4;
balancer1 = LoadBalancer.getLoadBalancer();
balancer2 = LoadBalancer.getLoadBalancer();
balancer3 = LoadBalancer.getLoadBalancer();
balancer4 = LoadBalancer.getLoadBalancer();
//判断负载均衡器是否相同
if(balancer1 == balancer2 &&balancer2 == balancer3 &&balancer3 == balancer4){
System.out.println("负载均衡器具有唯一性");
}
//增加服务器
balancer1.addServer("Server1");
balancer1.addServer("Server2");
balancer1.addServer("Server3");
balancer1.addServer("Server4");
//模拟客户端请求的分发
for(int i = 0;i<20;i++){
String server = balancer1.getServer();
System.out.println("分发请求至服务器:"+server);
}
}
}
(3)输出结果
5.饿汉式单例与懒汉式单例
(1)饿汉式单例
在定义静态变量的时候实例化单例类。
/**
* 饿汉式单例
*/
public class EagerSingleton {
private static EagerSingleton instance = new EagerSingleton();
//私有构造函数
private EagerSingleton(){
}
//静态共有工厂方法,返回唯一实例
public static EagerSingleton getInstance(){
return instance;
}
}
(2)懒汉式单例与双重检查锁定
懒汉式单例被加载时不会自己实例化,而是在第一次调用getInstance()方法时实例化。
但这里要注意,如果多线程同时调用getInstance()方法,可以使用关键字synchronized。但是用synchronized有一个不好的地方:每次调用getInstance()需要进行线程锁定判断,会降低系统性能。
因此采用双重检查锁定方法保证线程安全
/**
* 懒汉式单例
*/
public class LazySingleton {
//被volatile修饰的成员变量可以确保多个线程都能正确处理
private volatile static LazySingleton instance = null;
//私有构造函数
private LazySingleton(){
}
public static LazySingleton getInstance(){
//第一重判断
if(instance == null){
//锁定代码块
synchronized (LazySingleton.class){
//第二重判断,主要用于多线程同时进了这里,第一个创建了,第二个就不需要再创建了
if(instance == null){
instance = new LazySingleton();//创建单例实例
}
}
}
return instance;
}
}
(3)饿汉式和懒汉式的比较
饿汉式:
优:
(1)无需考虑多线程
(2)调用速度反应时间均优于懒汉式
缺:
(1)资源利用效率低于懒汉式
(2)加载时间较长
(4)使用静态内部类实现单例模式
在单例类中增加一个静态内部类,在该内部类中创建单例对象,再将该对象通过getInstance()方法返回外部使用。
注:该方法适用于java,很多其他面向对象语音不支持。因为依托java虚拟机保证线程安全性
/**
* 该方法适用于java,很多其他面向对象语音不支持。因为依托java虚拟机保证线程安全性
*/
public class IODHSingleton {
private IODHSingleton(){
}
//静态内部类
private static class HolderClass{
private final static IODHSingleton instance = new IODHSingleton();
}
public static IODHSingleton getInstance(){
return HolderClass.instance;
}
public static void main(String[] args) {
IODHSingleton s1,s2;
s1 = IODHSingleton.getInstance();
s2 = IODHSingleton.getInstance();
System.out.println(s1 == s2);
}
}
6.单例模式的优缺点
优:
(1)提供了唯一实例的受控访问
(2)节约系统资源
(3)允许可变数量的实例
缺:
(1)拓展困难
(2)单例类职责过重
(3)实例化对象长时间不被使用会被GC回收掉
7.使用场景
(1)系统只需要一个实例对象
(2)只允许一个公共访问点