单例模式
确保某个类只有一个实例,并且自行实例化向系统中提供这个实例,这个类被称为单例类,它提供全局访问的方法,是一种创建型模式。
特点:
(1)自行实例化(公有静态方法new 实例)
(2)只有一个实例(构造方法设为私有,不让new实例,并且静态方法进行空指针检验)
(3)必须自行的向系统中提供这个实例(公有静态方法返回)
简单版单例模式
/**
* 演示单例模式 任务管理器
*/
public class TaskManager {
private TaskManager taskManager = null;
// 这里要可见性为私有 防止被new一个实例
private TaskManager(){
}
private void displayTask(){
System.out.println("进程1");
}
// 静态方法,无须
public static TaskManager getInstance(){
if (taskManager == null){
taskManager = new TaskManager();
}
return taskManager;
}
}
案例
一.负载均衡设计
package com.learn.designmode.mode.singleLeton;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* 模拟负载均衡设计,模拟随机向服务器转发客户端请求
*/
public class LoadBalance {
/**
* 服务器集群
*/
private List<String> serverList;
/**
* 私有变量存储唯一实例
*/
private static LoadBalance loadBalance;
// 私有静态构造方法实例化
private LoadBalance(){
serverList = new ArrayList<>();
}
public static LoadBalance getInstance(){
if (loadBalance == null){
loadBalance = new LoadBalance();
}
return loadBalance;
}
// 删除服务器
private void deleteServer(String server){
serverList.remove(server);
}
// 增加服务器
public void addServer(String server){
serverList.add(server);
}
// 随机转发服务器
public String getServer(){
Random random = new Random();
int index = random.nextInt(serverList.size());
return serverList.get(index);
}
}
package com.learn.designmode.mode.singleLeton.test;
import com.learn.designmode.mode.singleLeton.LoadBalance;
public class LoadBalanceTest {
public static void main(String[] args) {
LoadBalance loadBalance = LoadBalance.getInstance();
LoadBalance loadBalance1 = LoadBalance.getInstance();
LoadBalance loadBalance2 = LoadBalance.getInstance();
LoadBalance loadBalance3 = LoadBalance.getInstance();
if (loadBalance == loadBalance1 && loadBalance1 == loadBalance2 & loadBalance2 == loadBalance3){
System.out.println("服务器负载均衡具有唯一性");
}
loadBalance.addServer("server1");
loadBalance.addServer("server2");
loadBalance.addServer("server3");
loadBalance.addServer("server4");
for (Integer i = 0;i<= 10;i++){
System.out.println("转发服务器" + loadBalance.getServer());
}
}
}
二.饿汉式单例和懒汉式单例的讨论
以上作为简单的单例模式的使用,但是在高并发的情况下会发生异常,原因在于第一次调用getInstance方法时,instance为null,将执行new LoadBalance的操作,在此过程中需要大量的初始化工作,需要一定的时间创建自己的实例化对象,如果此时再调用一次getInstacne方法 判断条件
if (loadBalance == null){
loadBalance = new LoadBalance();
}
将被再次执行,导致创建了多个instance实例,违背了单例模式的初衷,导致运行错误。
(1)饿汉式单例模式
饿汉式单例模式是最简单的单例类结构图如下
package com.learn.designmode.mode.singleLeton;
public class EagerSingleton {
// 将自身成员变量私有静态常量化 在类加载的时候初始化自身实例
private static final EagerSingleton eagerSingleton= new EagerSingleton();
private EagerSingleton(){
}
//
public static EagerSingleton getInstance(){
return eagerSingleton;
}
}
(2)懒汉式单例与线程锁定
懒汉式单例用到了延迟加载的技术,在类加载的时候不实例化对象,在需要的时候实例化,但是为了避免多个线程同时调用getInstance方法,使用synchronized锁住getInstance方法
package com.learn.designmode.mode.singleLeton;
public class LayzerSingleton {
// 不作为常量进行实例化
private static LayzerSingleton layzerSingleton = null;
private LayzerSingleton(){
}
synchronized public static LayzerSingleton getInstance(){
if (layzerSingleton == null){
layzerSingleton = new LayzerSingleton();
}
return layzerSingleton;
}
}
此方式存在性能问题,现实中不需要锁住整个获取实例的方法,只需要锁住判断语句后的创建实例的操作。
// 改进后的懒汉式单例模式
public static LayzerSingleton getInstance1(){
if (layzerSingleton == null){
synchronized (LayzerSingleton.class){
layzerSingleton = new LayzerSingleton();
}
}
return layzerSingleton;
}
上述改进还是会出现多个实例的隐患,原因在于假设某一瞬间有两个线程A,B同时调用getInstance方法,均通过了空值校验的判读语句,由于有了加锁机制,当线程A进入synchronized锁定的代码中创建了实例,线程B处于等待状态,当线程A执行完毕后,线程B并不知道实例已经被创建,判断语句过掉执行创建实例,导致创建了多个实例,违背了单例模式的思想,因此需要一种**“双重检查锁定”**的方式
// 改进后的双重检验锁懒汉式单例模式
private static volatile LayzerSingleton layzerSingleton1= null;
// 双重校验锁的懒汉式单例模式
public static LayzerSingleton getInstance2(){
if (layzerSingleton1 == null){
synchronized (LayzerSingleton.class){
if (layzerSingleton1 == null){
layzerSingleton1 = new LayzerSingleton();
}
}
}
return layzerSingleton1;
}
饿汉式单例模式和懒汉式单例模式的比较
一种更好的单例模式(静态内部类)
package com.learn.designmode.mode.singleLeton;
public class Singleton {
private static class HolderClass{
private static Singleton singleton = new Singleton();
}
public static Singleton getInstance(){
return HolderClass.singleton;
}
}
单例模式总结
主要优点
(1)单例模式提供了唯一实例的受控访问,因为单例类封装了它唯一实例,所以它可以严格控制客户以及如何访问它。
(2)由于在系统中只存在一个对象,因此可以节约资源,对于一些需要频繁创建和销毁的对象,单例模式无疑提高了性能。
(3)允许可变的数目,基于单例模式,开发人员可进行扩展,使用与控制单例对象相似的方法来获得指定个数的实例对象,既节省资源,有解决了由于单例模式共享过多产生的问题。
主要缺点
(1)由于单例模式没有抽象层,因此单例类的扩展很大难度
(2)单例类的职责过重,在一定程度上违背了单一原则,单例类又提供了业务方法,又提供了创建对象的方法,将对象的创建于功能耦合在一起
(3)很多面向对象设计语言都提供了垃圾回收机制,因此实例化共享长时间不被利用,系统会认为它是垃圾,会自动回收并销毁资源,下次利用又被实例化,导致共享的单例对象状态丢失。
使用场景
(1)系统只需要一个实例对象,比如唯一的序列号生成器或者资源管理器,或者考虑资源消耗太大而只允许创建一个实例
(2)客户端调用类的单个实例只允许一个公共的访问节点,除了该访问节点,不能通过其他节点访问该实例。