帮助了解Java线程池的核心原理

我们知道,在CS模式下的服务器端,服务器每侦听到一个客户端连接请求时,就产生一个线程,负责与客户端的持续通信,不断侦听来自客户端发来的“请求”。如果存在大量的客户端连接,服务器端就会产生出大量的线程,一直保持和客户端的通信,是长连接模式,这种情况下,线程存在的时间较长,不存在短期内大量线程的创建和销毁工作。
但是在RMI模式下的服务器端,服务器每侦听到一个客户端连接请求,就需要产生一个线程,完成客户端对特定方法的调用,具体的操作是在服务器端完成的,在客户端就只是调用一个方法,所以这个线程很快就会结束。那么,在大量的客户端都向服务器发送RMI请求时,会存在短期内大量线程的“创建”和“销毁”。而事实上,线程的“创建”和“销毁”是非常耗时、耗资源的,如果大量线程频繁的进行创建个销毁操作,会造成服务器的负担增大,从而导致服务器效率降低。

“线程池”的提出就是为了解决上面存在的问题,先提前创建一些线程,当某个线程运行结束后,并不是直接销毁,而是把它存放在一个池子中,即“线程池”,等再需要用到线程的时候,就从池子里取出一个线程,这样就避免了线程频繁的创建和销毁操作。

要注意的是,并不是说有了线程池之后,就完成不需要线程的创建工作了,这种说法是不对的,不是不创建线程,只是创建线程的次数没有那么频繁了,以此减少服务器的负担。

下面简单来给出线程池的核心操作,和真正的java线程池的核心操作差远了,所有操作都是为了让大家更好的理解线程池的作用原理,给出自己实现的简单线程池操作,来看:
首先给出一个接口,里面是要实现的线程操作的方法,

public interface ITasker {
	void beforeTask();
	void dealTask();
	void afterTask();
}

有关线程池的类,这里为了说明情况,区分空闲线程和工作线程:

private final Queue<Worker> freeThreadList;
private final List<Worker> busyThreadList;
注意,这里把空闲线程设置为一个队列Queue,而把工作线程给成普通列表List,是因为Queue队列的进出原则是“从头部出,尾部进”,每次需要用到一个线程时,都从空闲线程的队列头部拿出一个来使用,在这个队列中并不对空闲线程做任何区分,所以从头部取出是合理的,也是方便的;

之前说,之前创建好一些线程,可以设置参数是线程个数,这样就可以一次性创建多个线程,加入到空闲线程的队列当中:

private void creatWorker(int count) {
	for(int i = 0 ; i < count ; i ++) {
		Worker worker = new Worker();
		worker.startWorker();
		freeThreadList.add(worker);
	}
}

这个创建线程的方法可以在线程池类实例化的时候就调用,这样在刚开始就创建了一些线程以供使用,也可以在从空闲线程的队列中去线程的时候发现空闲线程队列中的线程不够用了,就再去创建一些(我们这里只学习线程池的核心原理操作,并不对java线程池做完整实现)。

补充,Java线程池中的线程创建不是一味的无休止的创建,线程池有它自己的创建线程的时间,在java的线程池中有两个有关线程池大小的参数,分别是
corePoolSize(线程池基本大小)和maximumPoolSize(线程池最大大小),
当某一次需要从线程池中获取一个空闲线程使用时,发现此时线程池中已创建的线程数量小于corePoolSize时,即便此时线程池中还有可以使用的空闲线程,也进行线程的创建,直到线程池的大小等于或者大于corePoolSize。另外当空闲线程的队列已满,而此时已创建的线程数量还未达到maximumPoolSize线程池允许存在的最大线程数量,可以继续创建线程,还有一种情况,如果此时需要获取一个空闲线程,而在线程池中的已创建线程又达到了maximumPoolSize的值,那么就不要立刻给出回应,等待一段时间,到线程池有空余线程的时候,再来从线程池中获取线程。

当需要用到线程时,从空闲线程的队列中获取一个线程,加到工作线程的列表中,而当该线程结束工作之后,并不释放,将该线程从工作线程移入到空闲线程队列中,以便下一次的获取使用,达到“线程复用”的效果,所以线程池还应该存在将某个已结束工作的线程变成空闲线程的作用,

public void setTask(ITasker tasker) {
	Worker worker = freeThreadList.poll();
	if(worker == null) {
		creatWorker(3);
		worker = freeThreadList.poll();
	}
	worker.setTasker(tasker);
	busyThreadList.add(worker);
	
}

注:有关在java中的队列删除增加操作可以查看区分java中Queue的增删操作

void workerFree(Worker worker) {
	busyThreadList.remove(worker);
	 if(freeThreadList.size() < 30) {
		 freeThreadList.offer(worker);
	 } else {
		 worker.stopWorker();
	 } 
}

线程结束工作后并不释放,有关线程真正的释放工作在线程池结束时进行,先检测有没有线程在工作,即检测工作线程的列表是否为空,如果已经没有线程工作了,就依次释放空闲线程的队列中还处于就绪态的线程,之后再结束整个线程池,如果结束线程池的时候还有线程在工作,有两种处理方法,“强制结束”就是忽略还在工作的线程,直接结束整个线程池,还有就是“等待结束”,等到所有线程都结束工作了,最后再结束整个线程池。

public boolean stopThreadPool() {
	Worker worker;
	if(busyThreadList.isEmpty()) {
		while((worker = freeThreadList.poll()) != null) {
			worker.stopWorker();
		}
		return true;
	}
	return false;
}

再给出单个的Worker 线程类,是Runnable的实现类,

public class Worker implements Runnable{

	private volatile ITasker tasker;
	private volatile boolean goon;
	
	private ThreadPool threadPool;
	
	public Worker() {
		this.threadPool = new ThreadPool();
	}
	
	public Worker(ITasker tasker) {
		this.tasker = tasker;
	}
	
	public void setTasker(ITasker tasker) {
		this.tasker = tasker;
	}

	public void startWorker() {
		goon = true;
		new Thread(this).start();
	}
	
	public void stopWorker() {
		goon = false;
	}
	
	@Override
	public void run() {
		while(goon) {
			if(tasker != null) {
				tasker.beforeTask();
				try {
					tasker.dealTask();
				} catch (Throwable t) {
					t.printStackTrace();
				}
				tasker.afterTask();
				tasker = null;
				threadPool.workerFree(this);
			}
		}
	}
}

在上述代码中可以看到,当一个线程结束工作之后,在线程池中将该线程由工作状态变为空闲状态,即把该线程从工作线程列表移入到空闲线程队列中。

可能有人会觉得在单个线程中需要对tasker加个锁,万一程序在执行完beforeTask()之后,时间片段到了,而在这之后外部又进行了setTask的操作,那么等到下一次轮到它执行的时候,tasker已经更改了,那怎么办?实际上,在上述的代码中,通过在逻辑上已经避免了这个问题,因为我们针对线程做了区分,有了工作线程列表和空闲线程对列,这样在每次获取线程的时候都是在空闲队列中取得,而且每次当一个线程结束工作之后都会将tasker赋值为null,所以根本不存在下一次执行操作的时候tasker已经被更改的问题。

可以看到在Worker 类中,将tasker成员设置为了volatile的,这是为了避免寄存器的优化,当tasker在某一个线程中起作用时,有可能另一个线程正在进行set操作,那么加上volatile就是为了避免在某次线程中判断tasker的值的时候,内存和寄存器中tasker的值不一致,所以每次都从内存中取值,虽然从内存中取值,会增加时间复杂度,效率会低一点,但是为了避免值受到干扰,时间复杂度是不可避免的。

最后,还有一点需要说明,前面提到说,时间片段到了的问题,这里对线程调度简单说明一下,
线程调度过程是指:
当某一个线程在执行过程中,时间片段到了,线程调度进程需要先“保护现场”,即,需要将当前正在执行的线程暂时中断,并将这个线程当前执行的状态保存起来,以便下一次该进程竞争到CPU之后,继续接着执行该线程时,能够接着被中断的程序继续正常执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值