java线程池ー自定义线程池

本文中内容是从B站中看到一个视频的案例(https://www.bilibili.com/video/BV16J411h7Rd?p=200),
为了加深理解,特抄录如下:(详细说明都记录在comment里面了)
由于@Slf4j还没有调试通,故没办法只有sysout。。。

package java_proj_test;

import java.util.ArrayDeque;
import java.util.Deque;
import java.util.HashSet;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import lombok.extern.slf4j.Slf4j;

/**
 * 自定义线程池
 * 
 * @author tei
 *
 */
@Slf4j(topic = "c.TestPool")
public class TestPool {

	public static void main(String args[]) {
		Logger log = LoggerFactory.getLogger(TestPool.class);

		ThreadPool threadPool = new ThreadPool(2, 1000, TimeUnit.MILLISECONDS, 10);
		System.out.println(Thread.currentThread().getName() + "线程池对象 :" + threadPool);
		for (int i = 0; i < 5; i++) {
			int tmp = i;
			threadPool.excute(() -> {
				System.out.println(Thread.currentThread().getName() + tmp);// log.debug({},tmp);
			});
		}
		log.debug("执行完成");
	}
}

// 2.线程池,就是一个线程的集合
@Slf4j(topic = "c.ThreadPool")
class ThreadPool {

	// 任务队列
	private BlokingQueue<Runnable> taskQueue;

	// 线程集合
	HashSet<Worker> workers = new HashSet<Worker>();

	// 核心线程数,即常驻内存的线程线程数
	private int coreSize;

	// 获取任务的超时时间(当没有任务的时候,让线程一只运行也是一种资源浪费,所以当超过一定时间,应当回收线程)
	private long timeOut;

	// 时间单位/时间单元
	private TimeUnit timeUnit;

	// 构造方法
	public ThreadPool(int coreSize, long timeOut, TimeUnit timeUnit, int queueCapcity) {
		this.coreSize = coreSize;
		this.timeOut = timeOut;
		this.timeUnit = timeUnit;
		this.taskQueue = new BlokingQueue<Runnable>(queueCapcity);
	}

	// 执行任务方法,要传递一个Runnable类型的任务对象,交给Worker对象来执行
	public void excute(Runnable task) {
		// 当当前的任务数没有超过核心线程数的时候呢,将该任务直接交给线程去执行;順便將该线程加入到线程集合
		// 当超过当前核心线程数时,应该把该任务加入任务队列,暂存起来,等有核心线程空闲时在来执行该任务;

		// 由于works这个集合是共享的,所以应当包证他的安全性
		synchronized (workers) {
			if (workers.size() < coreSize) {
				Worker worker = new Worker(task);
				// log.debug("新增worker {} {}", worker ,task)
				System.out.println("Thread_" + Thread.currentThread().getId() + "新增worker " + worker + " " + task);
				workers.add(worker);
				worker.start();
			} else {
				// log.debug("加入任务队列 {}",task)
				System.out.println("Thread_" + Thread.currentThread().getId() + "加入任务队列" + task);
				taskQueue.putElement(task);
			}

		}
	}

	/**
	 * 包装线程类,应该是一个线程对象, 并将要执行的任务交给线程对象-构造方法来传递任务
	 * 
	 * @author tei
	 *
	 */
	class Worker extends Thread {

		private Runnable task;

		public Worker(Runnable task) {
			this.task = task;
		}

		@Override
		public void run() {
			// 执行任务,执行的时候有2种可能:
			// [1] 马上执行该任务,( 因为构造的时候就已经将,任务传递过来了)
			// [2] 当任务已经执行完了,也不能马上结束当前线程,应该看一下任务队列中还有没有未执行的任务,
			// 如果有的话,则应该从队列中取出任务继续执行
			while (task != null || (task = taskQueue.pollElement(timeOut, timeUnit)) != null) { // 当没有任务的话,就等待一定时间,超时后自动结束
//				while (task != null || (task = taskQueue.takeElement()) != null) {	// 当没有任务也一只等待

				try {
					// log.debug("正在执行。。。{}",task);
					System.out.println("Thread_" + Thread.currentThread().getId() + "正在执行... " + task);
					task.run(); // 执行任务
				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					task = null;
				}
			}
			// 一旦退出了上面的循环,则表示该线程结束,应当从线程集合中移除
			synchronized (workers) {
				// log.debug("worker 被移除 {}",this);
				System.out.println("Thread_" + Thread.currentThread().getId() + "worker 被移除 " + this);
				workers.remove(this);
			}
		}
	}
}

// 1.阻塞队列
@Slf4j(topic = "c.BlokingQueue")
class BlokingQueue<T> {

	// 1.任务队列 (<LinkedList和ArrayDeque都是双向队列,性能上ArrayDeque更优)
	private Deque<T> queue = new ArrayDeque();
	// private Deque<T> queue = new LinkedList();

	// 2.锁
	private ReentrantLock lock = new ReentrantLock();

	// 3. 生产者/消费者的条件变量-> 等待
	// [1] 一般来讲,这个队列是有容量限制的,同理生产者也是有一定容量的限制,否则内存溢出有可能
	private Condition fullWaitSet = lock.newCondition();

	// [2] 消费者的条件变量-> 等待
	private Condition emptyWaitSet = lock.newCondition();

	// 4.队列的容量上限
	private int capcity;

	// 构造方法
	public BlokingQueue(int capcity) {
		this.capcity = capcity;
	}

	// [1] 阻塞获取
	public T takeElement() {
		// [1]获取元素需要用锁来保护,最后将锁释放
		lock.lock();
		try {
			// [2]如果队列中没有元素了,则应该将这个队列阻塞住,得等有元素了才可以执行
			while (queue.isEmpty()) {
				try {
					// [3] 让消费者等待
					emptyWaitSet.await();

				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			// [4] 当队列不空了,就可以继续执行,获取队列的头部元素,返回
			T t = queue.removeFirst();

			// [5] 当取走元素后,说明队列又有空位置了,则应当将生产者唤醒
			fullWaitSet.signal();

			return t;

		} finally {
			lock.unlock();
		}
	}

	// [2]阻塞添加
	public void putElement(T element) {
		// [1]添加元素需要用锁来保护,避免多個生产者同時添加,最后将锁释放
		lock.lock();
		try {
			// [2]添加的时候需要看队列是否已经满了?
			// 如果是的话,則不能添加,应该将这个队列阻塞住,得等沒有元素了才可以添加
			while (queue.size() == capcity) {
				try {
					// [3] 让生产者等待
					fullWaitSet.await();// await() 是一只等待,永久阻塞

				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			// [4] 当队列不满了,就可以继续执行,向队列尾部添加元素
			queue.add(element);

			// [5] 当添加进去元素了,就应当将消费者唤醒
			emptyWaitSet.signal();

		} finally {
			lock.unlock();
		}
	}

	// 5.自定义2个方法:
	// [1]从队列中获取元素的方法take [2]向队列中添加元素的方法put

	// [3] 获取队列大小,希望指定当前队列中排队执行的任务还有几个
	public int size() {
		lock.lock();
		try {
			return queue.size();
		} finally {
			lock.unlock();
		}
	}

	// [1] 阻塞获取改进为带超时的阻塞获取
	// [1] 改进为超时等待而不是一直等待,因此需要添加1.最大等待时间,2.时间单位--TimeUnit(方便时间单位的转换)
	public T pollElement(long timeOut, TimeUnit unit) {
		lock.lock();
		try {
			// [1] 将TimeOut统一转换为纳秒
			long nanos = unit.toNanos(timeOut);
			while (queue.isEmpty()) {
				try {
					// [2] 改进为等待一定时间,而不必死等了,而此时唤醒后如果它还是没等够的话-->虚假唤醒?
					// 再次循环就还得等那么长的时间,刚才的等待就等于白等了
					// awaitNanos()这个方法好就好在他的返回值,是timeOut的时间减去刚才已经等待的时间,
					// 因此我们把这个返回值再一次交给刚才的<nanos>这个变量,让其继续等待剩余的时间,
					// 所以,返回值就等于剩余时间,但是如果返回值小于或者等于0,那说明已经超时了,应当返回null
					if (nanos <= 0)
						return null;
					nanos = emptyWaitSet.awaitNanos(nanos);

				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			T t = queue.removeFirst();

			fullWaitSet.signal();

			return t;

		} finally {
			lock.unlock();
		}

	}
}

运行结果:

案例1:线程常驻内存,一只等待,

在这里插入图片描述
线程一只死死等待的原因:
在这里插入图片描述

案例2:线程等待一定时间,若超时,则自动结束

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值