单例模式的拓展应用——类最多可以实例化指定个数的对象

写在前面

因为让类只能实例化指定个数的对象需要有单例模式的基础,所以博主对单例模式进行了解释,如果想看本题代码实现的可以直接跳到最后

题目设计

  • 写一个类LimitInstanceClass,该类最多可以实例化指定个数实例。实例的个数用配置文件InstanceLimit.cfg指定。例如,如果InstanceLimit.cfg的内容为2,则LimitInstanceClass最多可以同时存在2个对象。

  • LimitInstanceClass的对象有一个整型成员变量id,保存对象的编号

    ;有一个boolean型变量isBusy,如果该变量的值为true,表示该对象正在被使用,否则该对象空闲。getInstance()方法判断是否存在空闲的对象,存在将该空闲对象的isBusy置为true,并返回该对象;如果不存在空闲对象则返回null。LimitInstanceClass有一个release()方法,该方法将对象的isBusy置为false。LimitInstanceClass还有一个String类型的成员变量accessMessage,以及一个成员方法writeAccessMessage(String message),该方法将参数message追加到accessMessage。LimitInstanceClass的printAccessMessage()方法输出accessMessage的内容。

  • 编写一个线程类AccessLimitInstanceClassThread,在其run()方法中获取一个LimitInstanceClass对象,调用获得的对象的writeAccessMessage(String message)将自己的线程名写入accessMessage,随机休眠0-5秒,再调用printAccessMessage(),最后调用release()方法。

  • 编写一个UseLimitInstanceClass类,在其main方法中实例化10个AccessLimitInstanceClassThread线程对象,并启动各个线程。设置InstanceLimit.cfg的内容为3,写出你的程序的运行结果。

单例模式

首先来看一下单例模式怎么实现,即怎么让一个类只能实例化一个对象

**意图:**保证一个类仅有一个实例,并提供一个访问它的全局访问点。

**主要解决:**一个全局使用的类频繁地创建与销毁。

**何时使用:**当您想控制实例数目,节省系统资源的时候。

**如何解决:**判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

**关键代码:**构造函数是私有的。

应用实例:

  • 1、一个班级只有一个班主任。
  • 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  • 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

优点:

  • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 2、避免对资源的多重占用(比如写文件操作)。

**缺点:**没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

使用场景:

  • 1、要求生产唯一序列号。
  • 2、WEB 中的计数器,不用每次刷新都在数据库里加一次,用单例先缓存起来。
  • 3、创建的一个对象需要消耗的资源过多,比如 I/O 与数据库的连接等。
public class Singleton {  
    private static Singleton instance;  
    private Singleton (){}  
    public static synchronized Singleton getInstance() {  
        if (instance == null) {  
            instance = new Singleton();  
        }  
        return instance;  
    }  
}

这种方式是最基本的单例模式实现方式,但仍然存在一些问题,如运行效率低等,但在这里不再展开。

步入正题

与单例模式的实现不同,因为要实例化指定个数的对象,所以需要一个对象数组,同时将指定个数的对象实例化后填入对象数组否则只是生成了指定长度的对象数组,数组内都是null

代码实现

InstanceLimit.xml

这是xml文件,用于读取实例化个数。博主没有根据题设从cfg文件中读取而是改从xml文件中读取实例化个数

该XML文件放在和其他Java文件平级的目录下(同一个package下)

<?xml version="1.0" encoding="UTF-8"?>
<project>
	<LimitInstance>
	    <length>3</length>
    </LimitInstance>
</project>

ParseXML.java

该类用于读取XML文件的内容

package limitInstance;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 * @author 作者 Long:
 * @version 创建时间:2022年4月27日 下午9:58:50
 */
public class ParseXML {
	// 用Element方式
	public static void element(NodeList list) {
		for (int i = 0; i < list.getLength(); i++) {
			Element element = (Element) list.item(i);
			NodeList childNodes = element.getChildNodes();
			for (int j = 0; j < childNodes.getLength(); j++) {
				if (childNodes.item(j).getNodeType() == Node.ELEMENT_NODE) {
					// 获取节点
					System.out.print(childNodes.item(j).getNodeName() + ":");
					// 获取节点值
					System.out.println(childNodes.item(j).getFirstChild().getNodeValue());
				}
			}
		}
	}

//	获取长度
	public static int getLength() {
		NodeList list = null;
		// 1.创建DocumentBuilderFactory对象
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		// 2.创建DocumentBuilder对象
		try {
			DocumentBuilder builder = factory.newDocumentBuilder();
			Document d = builder.parse("src/limitInstance/InstanceLimit.xml");
			list = d.getElementsByTagName("LimitInstance");
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		for (int i = 0; i < list.getLength(); i++) {
			Element element = (Element) list.item(i);
			NodeList childNodes = element.getChildNodes();
			for (int j = 0; j < childNodes.getLength(); j++) {
				if (childNodes.item(j).getNodeType() == Node.ELEMENT_NODE) {
					// 获取节点
					if (childNodes.item(j).getNodeName().equals("length")) {
						// 获取节点值
						return Integer.parseInt(childNodes.item(j).getFirstChild().getNodeValue());
					}

				}
			}
		}
		return 0;
	}

}

LimitInstanceClass.java

这是所有代码的核心,限制实例化个数的类,对象数组的生成和将对象填充到对象数组都封装为static静态的,使他们在类封装的时候就提前执行

同时博主增加了函数getUnBusyNum()来输出空闲的对象数目

package limitInstance;

/**
 * @author 作者 Long:
 * @version 创建时间:2022年4月27日 下午10:39:05
 */
public class LimitInstanceClass {
	private static int num = ParseXML.getLength();

	private int id;

	private String accessMessage;

	private static LimitInstanceClass[] instanceList = new LimitInstanceClass[num];

	static {
		instanceList = new LimitInstanceClass[num];
		for (int i = 0; i < instanceList.length; i++) {
			instanceList[i] = new LimitInstanceClass();
			instanceList[i].id = i;
		}
	}

	private LimitInstanceClass() {
		
	} // 声明私有构造函数,使得在本类外不能实例化本类对象
	
	public int getId() {
		return id;
	}
	
	private boolean isBusy = false;

	public static synchronized LimitInstanceClass getInstance() {
//		if (instanceList == null) {
//			instanceList = new LimitInstanceClass[num];
//		}
//		System.out.println(instanceList.length);
		for (int i = 0; i < instanceList.length; i++) {
//			System.out.println(instanceList[i]);
			if (instanceList[i].isBusy == false) {
				instanceList[i].isBusy = true;
				return instanceList[i];
			}
		}
		return null;
	}

	public void release() {
		this.isBusy = false;
	}

	public void writeAccessMessage(String message) {
		this.accessMessage = message;
	}

	public String printAccessMessage() {
		return this.accessMessage;
	}

	public static int getUnBusyNum() {
		int num = 0;
		for (int i = 0; i < instanceList.length; i++) {
			if (instanceList[i].isBusy == false) {
				num++;
			}
		}
		return num;
	}

}

AccessLimitInstanceClassThread.java

线程类,run方法中是一个死循环,线程会不断申请调用对象

  1. 申请获取对象
  2. 将线程自己的写入
  3. 打印信息(“某某线程调用了某某对象”)
  4. 打印空闲的对象数目
  5. 释放对象
  6. 睡眠一段时间
package limitInstance;

import java.util.Random;

/** 
* @author 作者 Long: 
* @version 创建时间:2022年4月27日 下午10:30:09 
*/
public class AccessLimitInstanceClassThread extends Thread{
	private String name;
	 
    public AccessLimitInstanceClassThread(String name){
        this.name = name;
    }
	
	@Override
    public void run() {
		while (true) {
			LimitInstanceClass li = LimitInstanceClass.getInstance();
			if (li==null) {
				try {
					sleep(2000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				continue;
			}
			li.writeAccessMessage(name);
			try {
				sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("线程:"+li.printAccessMessage()+"   调用了对象:"+li.getId());
			System.out.println("剩余空闲变量:"+LimitInstanceClass.getUnBusyNum());
			li.release();
			try {
				sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
    }

}

Main.java

Main方法生成十个线程,并让他们分别运行,观察结果

package limitInstance;

/**
 * @author 作者 Long:
 * @version 创建时间:2022年4月27日 下午9:42:30
 */
public class Main {

	public static void main(String[] args) {
		AccessLimitInstanceClassThread thread1 = new AccessLimitInstanceClassThread("thread--1");
		thread1.start();
		AccessLimitInstanceClassThread thread2 = new AccessLimitInstanceClassThread("thread--2");
		thread2.start();
		AccessLimitInstanceClassThread thread3 = new AccessLimitInstanceClassThread("thread--3");
		thread3.start();
		AccessLimitInstanceClassThread thread4 = new AccessLimitInstanceClassThread("thread--4");
		thread4.start();
		AccessLimitInstanceClassThread thread5 = new AccessLimitInstanceClassThread("thread--5");
		thread5.start();
		AccessLimitInstanceClassThread thread6 = new AccessLimitInstanceClassThread("thread--6");
		thread6.start();
		AccessLimitInstanceClassThread thread7 = new AccessLimitInstanceClassThread("thread--7");
		thread7.start();
		AccessLimitInstanceClassThread thread8 = new AccessLimitInstanceClassThread("thread--8");
		thread8.start();
		AccessLimitInstanceClassThread thread9 = new AccessLimitInstanceClassThread("thread--9");
		thread9.start();
		AccessLimitInstanceClassThread thread10 = new AccessLimitInstanceClassThread("thread--10");
		thread10.start();
	}
}

运行截图

在这里插入图片描述

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值