接着上一篇博客的 一Java线程的等待/通知模型 ,没有看过的建议先看一下。下面我们用等待通知机制来实现一个线程池.

本文的代码放到了github上,地址如下: git@github.com:jiulu313/ThreadPool.git

线程的任务就以打印一行文本来模拟耗时的任务。主要代码如下:

1  定义一个任务的接口。

 /*
* 任务的接口
*/
public interface Task {
void doSomething();
}

2  实现一个具体的任务。

 /*
* 具体的任务
*/
public class PrintTask implements Task{ //打印一句话,睡一秒,来模拟耗时的任务
@Override
public void doSomething() {
System.out.println("任务:"+Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

3  实现工作线程

 /*
* 工作者线程
*/
public class Worker implements Runnable {
//线程是否正在运行
private boolean running = true; //保存Thread,方便start()
private Thread thread; //保存线程池的任务队列,作同步用
private LinkedList<Task> tasks; public void setThread(Thread thread) {
this.thread = thread;
} public void setTasks(LinkedList<Task> tasks) {
this.tasks = tasks;
} //启动此工作线程
public void start() {
if (thread != null) {
thread.start();
}
} // 关闭此工作线程
public void shutDown() {
running = false;
thread.interrupt();
} @Override
public void run() {
while (running) {
Task task = null; //对共享变量加锁,此处为任务队列,因为会有多个线程访问
synchronized (tasks) { //当条件不满足时,线程等待,见上一篇博文
while (tasks.isEmpty()) {
try {
//线程进入等待状态,并且释放锁
tasks.wait();
} catch (InterruptedException e) {
//感知到外部对此线程的中断操作
Thread.currentThread().interrupt();
return;
}
} //条件满足
task = tasks.removeFirst();
} //执行任务
if (task != null) {
task.doSomething();
}
}
}
}

4  创建一个线程池

 import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List; public class DefaultThreadPool implements ThreadPool {
private int maxWorksNum = 10;
private int minWorksNum = 1;
private int defaultWorksNum = 5; // 任务列表
private LinkedList<Task> tasks = new LinkedList<>(); // 工作线程列表
private LinkedList<Worker> workers = new LinkedList<>(); //工作线程个数
private int workerNum = defaultWorksNum; @Override
public void excute(Task task) {
// 添加一个工作,然后进行通知
if (task != null) {
synchronized (tasks) {
//添加到最后一个位置
tasks.addLast(task);
//通知等待的线程,有新的任务了
tasks.notify();
}
}
} // 关闭线程池
@Override
public void shutDown() {
for (Worker worker : workers) {
worker.shutDown();
}
} // 初始化工作者线程
public void initWorkers(int num) {
if (num > maxWorksNum) {
num = maxWorksNum;
} else if (num < minWorksNum) {
num = minWorksNum;
} else {
num = defaultWorksNum;
} for (int i = 0; i < workerNum; i++) {
//创建工作线程
Worker worker = new Worker(); //添加到工作队列
workers.add(worker); //新建一个线程对象,并将worker赋值
Thread thread = new Thread(worker); //设置线程对象,作启动,中断用
worker.setThread(thread); //设置任务队列,作同步用
worker.setTasks(tasks);
}
} // 启动线程池
public void start(){
if(workers != null){
for(Worker worker : workers){
//启动一个工作线程
worker.start();
}
}
} // 新增加工作线程,但是不能大于线程池最大线程数
@Override
public void addWorkers(int num) {
if (num <= 0) {
return;
} int remain = maxWorksNum - workerNum;
if (num > remain) {
num = remain;
} for (int i = 0; i < num; i++) {
Worker worker = new Worker();
workers.add(worker);
Thread thread = new Thread(worker);
thread.start();
} workerNum = workers.size();
} // 减少工作线程,至少留1个,不能减少到0
@Override
public void removeWorkers(int num) {
if(num >= workerNum || num <= 0){
return;
} for(int i =0;i<num;i++){
Worker worker = workers.getLast();
worker.shutDown();
} workerNum = workers.size();
} @Override
public int getTaskSize() {
return tasks.size();
} }

5  新建测试类

 public class ThreadPoolTest {
public static void main(String[] args) throws InterruptedException {
//1 新建一个线程池
DefaultThreadPool pool = new DefaultThreadPool(); //2 设置线程池的大小为5
pool.initWorkers(5); //3 启动线程池
pool.start(); //4 往任务队列里面添加任务
for(int i = 0;i<100;i++){
pool.excute(new PrintTask());
} }
}

在eclipse中运行,结果部分截图如下:

好了,一个线程池就这样,这只是一个小例子,提示线程池的原理

其实实现工作中,一个线程池要考虑的问题远比这个多,也更复杂。

其中比较重要的两个方面:

1 线程池开几个线程为最合适?

我们知道,线程不是开的越多越好,而要根据业务的场景,硬件的指标,带宽的大小等等

一般线程池的个数为CPU核心数的个数加1 ,google的建议。此外可能还要具体分析业务

大,中,小的业务需求,也是不一样的。

大任务:比如下载一部电影,可能要十几分钟甚至几十分钟的任务

中任务:比如下载一幅图片,有1M以上了到十几M的大小的。

小任务:比如下载的是游戏的ico,就十几K的到1M以下的。

小任务可以多开几个线程。

中任务的可以保守点。

大任务的尽量不要开的线程太多

具体值还需要看具体业务,具体场景。这些只是建议。

2 线程用哪种队列,也是和上面有关系。

今天就到这了,后续还会抽时间研究线程并发这块,希望对大家有帮忙。

二 Java利用等待/通知机制实现一个线程池的更多相关文章

  1. 【Java并发基础】使用“等待—通知”机制优化死锁中占用且等待解决方案

    前言 在前篇介绍死锁的文章中,我们破坏等待占用且等待条件时,用了一个死循环来获取两个账本对象. // 一次性申请转出账户和转入账户,直到成功 while(!actr.apply(this, targe ...

  2. Java Concurrency - wait & notify, 等待通知机制

    生产者消费者问题是一个常见的多线程同步案例:一组生产者线程和一组消费者线程共享一个初始状态为空.大小为 N 的缓冲区.只有当缓冲区没满的时候,生产者才能把消息放入缓冲区,否则必须等待:只有缓冲区不空的 ...

  3. 内置锁(二)synchronized下的等待通知机制

    一.等待/通知机制的简介 线程之间的协作:   为了完成某个任务,线程之间需要进行协作,采取的方式:中断.互斥,以及互斥上面的线程的挂起.唤醒:如:生成者--消费者模式.或者某个动作完成,可以唤醒下一 ...

  4. Java多线程之三volatile与等待通知机制示例

    原子性,可见性与有序性 在多线程中,线程同步的时候一般需要考虑原子性,可见性与有序性 原子性 原子性定义:一个操作或者多个操作在执行过程中要么全部执行完成,要么全部都不执行,不存在执行一部分的情况. ...

  5. java多线程系列(三)---等待通知机制

    等待通知机制 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解 ...

  6. Java并发编程,Condition的await和signal等待通知机制

    Condition简介 Object类是Java中所有类的父类, 在线程间实现通信的往往会应用到Object的几个方法: wait(),wait(long timeout),wait(long tim ...

  7. Java并发编程(04):线程间通信,等待/通知机制

    本文源码:GitHub·点这里 || GitEE·点这里 一.概念简介 1.线程通信 在操作系统中,线程是个独立的个体,但是在线程执行过程中,如果处理同一个业务逻辑,可能会产生资源争抢,导致并发问题, ...

  8. JMM之Java线程间通讯——等待通知机制及其经典范式

    在并发编程中,实际处理涉及两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体). 通信是指线程之间以何种机制来交换信息.在共享内存的并发模型里,线程之间共享程序的公共状 ...

  9. Java并发之等待/通知机制

    目录 1 前言 1.1 先来段代码放松一下 2 Object wait()/notify() 2.1 一段入门代码 2.2 问题三连击 a.为什么官方说wait() 要放在while里面? b.为什么 ...

随机推荐

  1. 配合crond服务实现自定义周期备份MySQL数据库(使用innobackupex进行备份)

    备份 新建一个脚本/root/backup.py,内容如下: #!/usr/bin/env python# -*- coding: utf-8 -*- ''' 脚本作者:昨夜星辰 脚本作用:配合cro ...

  2. xml对象的序列化和反序列化

    对象序列化: /// <summary>        /// 将一个对象序列化为XML字符串        /// </summary>        /// <par ...

  3. 贼溜的更新Android-SDK的方法(亲测很好用)

    启动 Android SDK Manager ,打开主界面,依次选择「Tools」.「Options...」,弹出『Android SDK Manager - Settings』窗口:在『Androi ...

  4. Linked List Start!

    (1)Delete Node in a Linked List 题意简单明了,用后一个节点来替换要删除的节点即可.代码如下: /** * Definition for singly-linked li ...

  5. 循序渐进Python3(八) -- 1 -- socket进阶

    IO多路复用 I/O多路复用指:通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作. Linux中的 select,poll,epoll 都 ...

  6. spark 获取applicationID

    在编写spark 程序时,有时需要获取job id 进行记录. 只需在程序中加入: sc.applicationId res0: String = app-- 每个job提交后, 均有自己的监控页面. ...

  7. [z]查表空间使用情况

    SELECT UPPER(F.TABLESPACE_NAME) "表空间名",D.TOT_GROOTTE_MB "表空间大小(M)",D.TOT_GROOTTE ...

  8. TCP协议学习记录 (二) Ping程序

    简单的实现了一个ping程序,没有做icmp差错报文的检查. 支持自定义字节数,支持发包个数 #pragma pack(4) #define ECHO_REQUEST 8 #define DATASI ...

  9. WPF 动态更换样式文件

    ApplySkinFromMenuItem("Style/BigImgStyle.xaml", "Style/FileListStyle.xaml"); //换 ...

  10. Java实现中文数字转换为阿拉伯数字

    /** * 中文數字转阿拉伯数组[十万九千零六十 --> 109060] * @author 雪见烟寒 * @param chineseNumber * @return */ @Suppress ...