🎏🎏🎏个人主页🎏🎏🎏
🎏🎏🎏JavaEE专栏🎏🎏🎏
🎏🎏🎏上一篇文章:多线程代码案例(1)🎏🎏🎏
文章目录
- 1.线程池
- 1.1概念
- 1.2线程池如何提高效率
- 1.3标准库线程池的参数分析
- 1.4模拟实现一个线程池
- 1.4.1一个固定数目的线程池
- 1.4.2含有最大线程数的线程池
- 1.5线程池优点
- 2.定时器
- 2.1概念
- 2.2Java标准库中的定时器(Timer)
- 2.3模拟实现定时器
- 2.3.1定时器的需求
- 2.3.2实现需求的技术
- 2.3.3代码的实现
- 1.4总结
1.线程池
1.1概念
将你需要用到的线程提前创建好,然后放到用户态通过数据结构的形式来管理。
1.2线程池如何提高效率
我们直接创建线程是内核态与用户态两一起配合完成的,如果频繁的去创建线程销毁线程,这样效率就会大大降低并且内核在完成一些任务是不可控的,面对这种情况我们就可以提前将我们需要用到的线程创建好放在用户态种通过数据结构管理起来,当需要使用的时候就通过用户态来调用,不要用就放回用户态中,减少内核态的参与相当于减少了不可控性那么效率就会提高。
1.3标准库线程池的参数分析
ThreadPoolExecutor
- int corePoolSize int maximumPoolSize
corepoolSize(核心线程数)——根据CPU的逻辑核心数决定的
maximumPoolSize(最大线程数)——由核心线程数+非核心线程数
对线程池中的线程分为两种:核心线程和非核心线程
核心线程:当线程池被创建了,核心线程也就有了。
非核心线程:当任务过多的时候,核心线程处理不过来的时候,线程池就会临时创建线程来分担任务,当任务变轻的时候,那么这些非核心线程就会被回收,这样当任务繁重的时候,增加一些非核心线程就会提高效率,当任务空闲的时候就可以减少开销。
那么最大的线程数应该设多少比较合适呢?
关于设多少比较合适不仅仅和电脑的配置有关系还和代码类型有关系。
代码类型在理想的情况下分为两种:CPU密集类型和IO密集类型
CPU密集类型:
代码中基本都是算术运算,条件判断,循环判断,函数调用这些都是需要大量调用CPU来参加工作的,那么这种最大的线程数应该要小于等于逻辑核心数。
IO密集类型:
IO类型的每一个线程消耗的CPU只有一点点,影响的主要是其他方面比如网卡带宽,硬盘访问…,那么最大线程数可以大于等于逻辑核心数。
但我们生活中可不会有这种理想情况发生,一般都是CPU密集型于IO密集型结合的情况,那么这个最大的线程数怎么去确定呢,可以根据做实验的方式(控制变量法)来确定一个相对正确的数据。 - long keepAliveTime TimeUnit unit
long keepAliveTime——允许非核心线程最大的空闲时间
TimeUnit unit——空闲时间单位
给定时间可以增加容错率,防止任务少的时候突然任务剧增,这样就可以给回收非核心线程缓冲时间。 - BlockingQueue workQueue
BlockingQueue workQueue——线程池的任务队列
线程池会提供submit方法,让其他线程将任务提交给线程池,此时的线程池就需要队列这样的数据结构,将任务管理起来,这个队列存储的元素其实就是Runnable对象,要执行的逻辑其实就是run方法中的内容 - ThreadFactory threadFactory
ThreadFactory threadFactory——java标准库中提供的工厂类
当我们需要创建一个点,有两种方式一种是笛卡尔坐标表示法,另一种是极坐标表示法
class Point {
point(double x,double y) {
}
point(double r,double a) {
}
}
此时的两种方式都是通过构造方法来创建的,但是这两种方法构成不了重载,所以无法实现,因为在某些特殊情况构造方法会带来一些麻烦,就出现了工厂方法来封装一些这些构造方法,这种模式叫工厂模式
class Point {
public static point makePointByXY(double x, double y) {
Point p = new Point();
p.set(x);
p.set(y);
return p;
}
public static point makePointByRA(double r, double a) {
Point p = new Point();
p.set(r);
p.set(a);
return p;
}
}
上述代码就是一种通过工厂方法来封装这些构造点的方法。
5. RejectedExecutionHandler handler
RejectedExecutionHandler handler——拒绝策略,是一种以枚举的方式表示的。
- AbortPolicy():超出负荷,直接抛出异常
- CallerRunsPolicy():调用者负责处理多出来的任务
- DiscardOldestPolicy():丢弃队列中最老的任务
- DiscardPolicy():丢弃新来的任务
由于ThreadPoolExecutor用起来非常费劲,于是就提供了几个工厂类例如:Executor
通过工厂类中提供的方法可以创建线程池的几种方式: - Executors.newFixedThreadPool()——创建固定线程数目的线程池
- Executors.newSingleThreadExecutor()——创建一个只包含单个线程的线程池
- Executors.newScheduledThreadPool(4)——创建一个固定线程个数, 但是任务延时执行的线程池
创建一个固定线程数的线程池:
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
for (int i = 0; i <=50000 ; i++) {
int id = i;
service.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello " + id + ", " + Thread.currentThread().getName());
}
});
}
}
1.4模拟实现一个线程池
- 需要若干个线程
- 需要任务队列
- 需要submit方法
1.4.1一个固定数目的线程池
//创建一个简单的线程
public class MyThreadPollBasicEdition {
//创建一个任务队列
public BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(1000);
//初始化线程池
public MyThreadPollBasicEdition(int n) {
for (int i = 0; i < n; i++) {
Thread t = new Thread(()-> {
try {
while(true) {
Runnable runnable = queue.take();
runnable.run();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t.start();
}
}
//提供submit方法
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
}
public static void main(String[] args) throws InterruptedException {
MyThreadPollBasicEdition myThreadPollBasicEdition = new MyThreadPollBasicEdition(10);
for (int i = 0; i < 50000; i++) {
int id = i;
myThreadPollBasicEdition.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello " + id + ", " + Thread.currentThread().getName());
}
});
}
}
}
1.4.2含有最大线程数的线程池
ic class MyThreadPollAdvancedEdition {
//创建一个任务队列的对象
public BlockingQueue<Runnable> queue = new ArrayBlockingQueue(1000);
//创建一个最大线程数
public int maxThreadSize = 0;
//创建一个集合来存储若干个线程
public List<Thread> threadList = new ArrayList<>();
//创建构造方法
public MyThreadPollAdvancedEdition(int corePoolSize, int maxThreadSize) {
this.maxThreadSize = maxThreadSize;
for (int i = 0; i < corePoolSize; i++) {
Thread t = new Thread(()->{
try {
while (true) {
Runnable runnable = queue.take();
runnable.run();
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
threadList.add(t);
}
}
public void submit(Runnable runnable) throws InterruptedException {
queue.put(runnable);
//如果队列中的任务过多,导致线程不够用,可以增加一些线程
if(queue.size() >=200 && threadList.size() < maxThreadSize) {
Thread thread = new Thread(()-> {
try {
while(true) {
Runnable runnable1 = queue.take();
runnable1.run();
}
}
catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
threadList.add(thread);
}
}
public static void main(String[] args) throws InterruptedException {
MyThreadPollAdvancedEdition myThreadPollAdvancedEdition = new MyThreadPollAdvancedEdition(10,20);
for (int i = 0; i < 50000; i++) {
int id = i;
myThreadPollAdvancedEdition.submit(new Runnable() {
@Override
public void run() {
System.out.println("hello " + id + ", " + Thread.currentThread().getName());
}
});
}
}
}
1.5线程池优点
- 降低资源消耗:减少线程的创建和销毁带来的性能开销。
- 提高响应速度:当任务来时可以直接使用,不用等待线程创建
- 可管理性: 进行统一的分配,监控,避免大量的线程间因互相抢占系统资源导致的阻塞现象。
2.定时器
2.1概念
用于实现定时操作、周期性任务和超时控制的作用
2.2Java标准库中的定时器(Timer)
Timer提供了一个schedule方法
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello,2000");
}
},2000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello,1000");
}
},1000);
timer.schedule(new TimerTask() {
@Override
public void run() {
System.out.println("hello,3000");
}
},3000);
}
2.3模拟实现定时器
2.3.1定时器的需求
- 能够延时执行任务和定时执行任务
- 能够管理多个任务
2.3.2实现需求的技术
- 需要一个描述任务和指定时间的类(本质就是一个Runnable)
- 需要一个数据结构来管理多个任务(优先级队列)
- 需要一个线程来扫描数据结构管理的任务
2.3.3代码的实现
//1.定义一个TimeTask类表示一个任务,这个类中需要任务执行的时间和描述任务。
class TimeTask implements Comparable<TimeTask> {
public Runnable runnable;
//此时的time不是程序等待的时间,而是一个绝对时间
public long time;
public TimeTask(Runnable runnable,long delay) {
this.runnable = runnable;
//手动换算时间,加一个时间戳将相对时间换算成绝对时间
this.time = System.currentTimeMillis() + delay;
}
public void run() {
runnable.run();
}
public long getTime() {
return time;
}
@Override
public int compareTo(TimeTask o) {
return (int) (this.time - o.time);
}
}
//2.定义一个数据结构来管理多个任务,此处我们优先级队列来管理多个任务,因为用其他的数据结构去管理的话,
// 就需要不断去扫描数据结构中满足要求的成员,遇到数据量庞大的那么开销就巨大,得不偿失,
// 用优先级队列可以避免这种情况,由于要求等待时间短的先运行,那么我们可以定义一个小根堆。
class MyTime{
Object locker = new Object();
public PriorityQueue<TimeTask> queue = new PriorityQueue<>();
//初始化线程,并且调用任务
public MyTime() {
//定义一个扫描线程来获取堆顶任务
Thread t = new Thread(()-> {
try {
while(true) {
synchronized (locker) {
if (queue.size() == 0) {
locker.wait();
}
//取小堆中堆顶的任务
TimeTask task = queue.peek();
//获取当前的时间戳
long curTime = System.currentTimeMillis();
//看任务的时间是否到了
if(curTime >= task.getTime()) {
//时间到了,则执行任务
task.run();
//任务执行完之后,则将队列中的堆顶任务消除
queue.poll();
} else {
//任务时间没到,则堵塞,阻塞多久?堵塞任务等待的时间
locker.wait(task.getTime() - curTime);
}
}
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t.start();
}
//提供一个schedule方法来创建任务
public void schedule(Runnable runnable,long delay) {
synchronized (locker) {
TimeTask timeTask = new TimeTask(runnable,delay);
queue.offer(timeTask);
//唤醒调用任务的线程
locker.notify();
}
}
}
public class MyTimerTest {
public static void main(String[] args) {
MyTime myTime = new MyTime();
myTime.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello,1000");
}
},1000);
myTime.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello,2000");
}
},2000);
myTime.schedule(new Runnable() {
@Override
public void run() {
System.out.println("hello,3000");
}
},3000);
}
}
1.4总结
- 创建一个类,表示一个任务(Runnable 任务本体 time任务的执行时间)
- 引入数据结构来管理多个任务(用的是优先级队列,省去遍历的开销)
- 引入扫描线程,不停的循环获取队列队首任务,判定是否到时间,到时间就执行,并且出队列没到时间就阻塞。
- 引入锁,针对队列出和入的操作
- 解决忙等问题,引入wait和notify,队列为空wait(死等)队首任务没到时间wait(带有超时时间)这里不要用sleep(sleep通过interrupt唤醒是非常规手段,sleep不会释放锁,会影响后续插入任务)
- 引入比较规则,让TimeTask可以按照时间先后来制定优先级。