Java多线程相关的内容,我分为几个章节分别介绍。
其中 Thread、Runnable、Callable创建线程为Java的线程基础,我作为第一节记述,后续是线程池的相关知识,循序渐进的阐述。
最后是一些线程方面的面试题,争取做到包含大厂和一些基础知识的面试题涵盖。
线程池其实就是一种多线程的处理方式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。
(这里的任务就是实现的Runnable或Callable接口的实例对象)
使用线程池最大的原因就是可以根据系统需求和硬件环境灵活的控制线程的数量,且可以对所有线程进行统一的管理和控制,从而提高系统的运行效率,降低系统运行压力。
优势:
1. 线程和任务分离,提升线程的重要性;
2. 控制线程并发数量,降低服务器压力,统一管理所有线程。
3. 提升系统响应速度,假如创建线程的时间t1,执行任务时间t2,撤销任务时间t3,那么使用线程池就免去了t1和t3的时间。
应用场景介绍
1.团购商品秒杀;
2.云盘文件上传下载;
3.12306网上购票系统
总之
只要有并发的地方、任务数量大或小、每个执行的时间长或短都可以使用线程池;只不过在使用线程池的时候,注意一下设置合理的线程池大小。
首先了解一下Java关于线程的API中的 “Java内置线程池”源码
java.util.concurrent.ThreadPoolExecutor
构造方法:
public ThreadPoolExecutor( int corePollSize, // 核心线程数量
int maximumPoolSize, // 最大线程数量
long keepAlicveTime, // 最大空闲时间
TimeUnit unit, //时间单位
BlockingQueue workQueue, //任务列队
ThreadFactory threadFactory, //线程工厂
RejectExecutionHandle handler //饱和处理啊机制 )
{...}
1:核心线程数(corePoolSize)
核心线程数的设计需要依赖任务的处理时间和每秒产生的任务数量来确定,例如执行一个任务需要0.1秒,系统百分之80的情况每秒产生100个任务,那么要想在一秒内处理啊完这100个任务,就需要10个线程,此时我们可以设计核心线程为10。
2:任务队列长度(workQueue)
任务长度一般设计为: 核心线程数 / (单个任务执行时间*2)
3:最大线程数(maximumPoolSize)
最大线程数 = ( 最大任务 - 任务队列长度 ) * 单个任务执行的时间。
4:最大空闲时间(keepAliveTime)
这个参数的设计完全参考系统运行环境和硬件压力设定,没有固定的参考值。
(包括以上三个参数,只是一般的设计原则,并不是固定的,用户也可以根据实际情况灵活调整)
1、编写任务类(MyTask),实现Runnable接口;
2、编写线程类(MyWork),用于执行任务,需要持有所有任务;
3、编写线程池类(My ThreadPool),包含执行任务,执行任务的能力;
4、编写测试类(MyTest),创建线程池对象,提交多个任务测试;
1、编写任务类(MyTask),实现Runnable接口;
package com.MyThread;
/*
要求:自定义线程练习,这是个任务类,需要实现Runnable 接口;
包含任务编号,每个任务类的执行时间为0.2秒;
* */
public class MyTask implements Runnable {
private int id;
//由于run方法是重写接口中的方法。因此id的属性初始化可以利用构造方法完成。
public MyTask(int id) {
this.id = id;
}
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println("线程" + name + "即将执行任务");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程" + name + "完成了任务");
}
}
2、编写线程类(MyWork)
package com.MyThread;
import java.util.List;
/*
需求:编写一个线程类,需要继承Thread类,设计一个属性,用于保存线程的名字;
设计一个集合,用于保存所有的任务;
* */
public class MyWork extends Thread {
//保存线程的名字
private String name;
//保存所有的任务
private List<Runnable> tasks;
public MyWork(String name, List<Runnable> tasks) {
super(name);
this.tasks = tasks;
}
@Override
public void run() {
//判断集合中是否有任务,只要有就一直执行。
while (tasks.size() > 0) {
Runnable r = tasks.remove(0);
r.run();
}
}
}
3、编写线程池类(My ThreadPool)
package com.MyThread;
import java.util.*;
/*
这是自定义线程类;
成员变量:
1:任务队列 集合 需要控制线程安全问题
2:当前线程数量
3:核心线程数量
4:最大线程数量
5:任务队列长度
成员方法
1:提交任务:
将任务添加到集合中,需要判断是否超出了任务长度。
2:执行任务
判断当前线程数量,判断创建核心线程还是非核心线程。
* */
public class MyThreadPool {
//1:任务队列 集合 需要控制线程安全问题
private List<Runnable> tasks = Collections.synchronizedList(new LinkedList<Runnable>());
//2:当前线程数量
private int num;
//3:核心线程数量
private int corePoolSizee;
//4:最大线程数量
private int maxSize;
//5:任务队列长度
private int workSize;
//1:提交任务:
public void submit(Runnable r) {
//将任务添加到集合中,需要判断是否超出了任务长度。
if (tasks.size() >= workSize) {
System.out.println("任务:" + r + "被丢弃了...");
} else {
tasks.add(r);
//执行任务
execTack(r);
}
}
//2:执行任务
private void execTack(Runnable r) {
//判断当前线程池中的线程总数量是否超出了核心数。
if (num < corePoolSizee) {
new MyWork("核心线程" + num, tasks).start();
num++;
} else if (num < maxSize) {
new MyWork("非核心线程" + num, tasks).start();
}
}
}
4、编写测试类(MyTest)
package com.MyThread;
/*
测试类:
1:创建线程池类对象
2:提交多个任务
* */
public class MyTest {
public static void main(String[] args) {
//1、创建一个线程池对象
MyThreadPool pool = new MyThreadPool(2, 4, 20);
//2:提交多个任务
for (int i = 0; i < 10; i++) {
//3:创建任务对象,提交给线程池
MyTask my = new MyTask(i);
pool.submit(my);
}
}
}