Java – 多线程 – 基础使用
简介
多线程分两个概念,分别是【并发】和【并行】。
并发指的是单个CPU在多个代码段之间进行游走执行,在一个时间段内,多个代码段只有一个能被执行。
并行指的是多个CPU在多个代码段之间同时执行,在一个时间段内,多个代码段能同时被执行。
多线程实现
方法一:继承Thread类
通过继承Thread
类,并重写run
方法。调用时先new
多线程类,调用start()
方法启动多线程。
public class MyThread extends Thread {
/*
* 重写run方法
* */
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":HelloWorld");
}
}
}
MyThread t1 = new MyThread();
t1.setName("线程1");
t1.start();
优缺点:
优点:编程比较简单,可以直接使用Thread类中的方法
缺点:可扩展性较差,不能再继承其它的类,不支持返回值
方法二:实现Runnable接口
通过类实现接口Runnable
中的run
方法。
创建对象,并创建Thread类,把多线程对象传入Thread类中调用start()
方法即可。
public class MyRunnable implements Runnable {
@Override
public void run() {
/**
* 通过 Thread.currentThread() 获取当前线程的信息,比如 getName() 方法等
*/
Thread t = Thread.currentThread();
for (int i = 0; i < 100; i++) {
System.out.println(t.getName() + ":HelloWorld");
}
}
}
// 创建多线程对象
MyRunnable m1 = new MyRunnable();
// 创建Thread对象
Thread t = new Thread(m1);
t.setName("线程1");
// 启动线程
t.start();
使用Lambda表达式
new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("" + i);
}
}).start();
优缺点:
优点:扩展性强,实现该接口的同时还可以继承其它的类
缺点:编程相对复杂,不能直接使用Thread类中的方法,不支持返回值
方法三:利用Callable接口和Future接口实现
这种方法可以获取到多线程运行的结果
1.创建一个类MyCallable实现Callable接口
2.重写call(有返回值,表示多线程要执行的任务)
3.创建MyCallable的对象(表示多线程要执行的任务)
4.创建FutureTask的对象(作用管理多线程运行的结果)
5.创建Thread类的对象,并启动(表示线程)
// 创建一个类MyCallable实现Callable接口
public class MyCallable implements Callable<Integer> {
/**
* 重写 call() 方法(有返回值,表示多线程要执行的任务)
* @return
* @throws Exception
*/
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
}
}
/**
* 创建多线程类的对象
* 创建MyCallable的对象(表示多线程要执行的任务)
*/
MyCallable mc = new MyCallable();
/**
* 创建 FutureTask 任务管理对象
* 因为 Callable 接口提供带有返回值的多线程方法
* 因此 FutureTask 是用于管理返回值的
* 创建FutureTask的对象(作用管理多线程运行的结果)
*/
FutureTask<Integer> ft = new FutureTask<>(mc);
/**
* 创建 Thread 线程对象
* 创建Thread类的对象,并启动(表示线程)
*/
Thread t = new Thread(ft);
/**
* 启动线程
*/
t.start();
/**
* 取得线程运行后的结果
*/
Integer result = ft.get();
System.out.println(result);
使用Lambda表达式
public static void CallableTwo() {
new Thread(new FutureTask<Integer>(() -> {
int sum = 0;
for (int i = 0; i < 100; i++) {
sum += i;
}
return sum;
})).start();
}
优缺点:
优点:扩展性强,实现该接口的同时还可以继承其它的类,支持返回值
缺点:编程相对复杂,不能直接使用Thread类中的方法
多线程常用方法
getName 获取线程名
返回此线程的名称。
注意:如果创建线程时不设置线程名称,默认会以【Thread-序号】命名。会从【Thread-0】开始,【Thread-1】、【Thread-2】...如此类推
Thread thread = new Thread("线程名");
thread.getName();
setName 设置线程名
设置线程的名字(在Thread类构造方法中也可以直接设置名字)
Thread thread = new Thread("线程名");
thread.setName("线程名");
注意:如果是继承Thread类的子类(如上文方法一),在构造函数中定义线程名会报错,这是因为子类中没有对应的构造函数,可以让子类重写父类的带参构造函数
static currentThread 获取当前线程
获取当前线程的对象,如果是在main函数中调用,则返回的是main线程(JVM主线程)
Thread.currentThread();
static sleep 线程休眠
让线程休眠指定的时间,单位为毫秒
Thread.sleep(1000);
setPriority 线程优先级
设置线程的优先级,优先给最低是1,最高是10,如果默认的优先级是5.
包括main线程在内的所有线程,默认优先给都是5.
优先给是指该线程是否更大概率争抢到CPU的执行权,但不保证一定,只是概率大小上更具优势。
// 使用 getPriority() 取得优先级
Thread.currentThread().getPriority(); // 取得 main 的优先级为5
// 设置优先级
Thread.currentThread().setPriority(10); // 设置优先级为10后,在多个线程运行过程中,该线程会总体运行更多代码
setDaemon 守护线程
设置守护线程,即守护线程会实时监测非守护线程的执行情况,如果非守护线程在执行过程,守护线程也会执行。当非守护线程执行结束,守护线程也会陆续结束(即使守护线程未执行完成都直接结束)
Thread t1 = new Thread("非守护线程");
Thread t2 = new Thread("守护线程");
// 设置 t2 为守护线程
t2.setDaemon(true);
/**
* 当t1没有结束时,t2依然运行,当t1运行结束时,t2不管是否运行完成都会马上结束
*/
t1.start();
t2.start();
static yield 协程、出让、礼让线程
在线程的运行过程中,CPU并非会绝对性的在多个线程代码中随机游走,而是可能会在对某一条线程执行过程中提供更久的处理资源,使得其它线程会在某些不确定的时候多等待一会。
举例子,比如线程A和线程B都同时执行100个循环,CPU实际执行过程中并非绝对性的【先A再B再A再B】的顺序执行,有可能是【AAAAAAAABBBBBBBABABBBBBB】的随机顺序。
因此,可以在代码中加入 yield 使得该线程执行完某一个循环时,主动出让处理权,使CPU在执行完成一次循环后,再一次公平选择执行A还是执行B,使得AB线程更均衡地执行。
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + ":HelloWorld");
// 执行完一次循环后,出让执行权让CPU重新选择执行
Thread.yield();
}
}
join 插入、插队线程
当本线程和多线程出现线程争夺时(如在main方法中调用多线程对象时,main线程和另一个线程争夺CPU处理权)
可以使用 join 强制插入到当前线程之前,优先处理。
Thread thread = new Thread("线程1");
// 使用插入线程后,执行 main 方法中的循环的资源将被优先提供给【线程1】处理完
thread.join();
thread.start();
// 在 main 方法中执行循环(使用 main 线程)
for (int i = 0; i < 100; i++) {
System.out.println(i);
}
多线程生命周期
线程锁
在多线程执行过程中,CPU对代码的执行是随机性的,也就是说CPU不会把一段代码执完完全后再给其它线程进行执行,有可能是执行了一半的代码就跑去执行其它线程了
这使得非常大的可能出现多条线程共同运行在同一段代码块中,引起部分变量被同时改变,影响变量在各个线程的精准度。
Java提供一种线程锁,在线程锁代码内,允许有且仅有一条线程在代码块中执行,其它线程只能在代码块外等待。
synchronized(锁对象){}
线程锁参照锁对象
来识别那些线程是一组的,同一组的线程在一个时间内只允许有一条线程在执行。
锁对象
只作为线程分组的参照,所以可以是任意对象,可以创建一个 Object 空对象作为锁对象,若多个线程希望分为一组,则执行线程的锁对象必须一样。
public class MyThread extends Thread {
private static Integer length = 100;
// 设定一个静态的对象,用于做线程锁的分组参照
private static Object object = new Object();
@Override
public void run() {
while (length > 0){
// 创建线程锁,使多个线程进去代码时,有且仅有一条线程能进入执行
synchronized (object){ // 通常使用 MyThread.class 作为锁对象
System.out.println(getName() + ":HelloWorld" + length);
length--;
}
}
}
}
同步方法
若整个方法都需要被加同步锁,不需要在方法中加入 synchronized ,可以使用同步方法的方式,用synchronized关键字修饰方法
public class MyThread extends Thread {
private static Integer length = 100;
// 设定一个静态的对象,用于做线程锁的分组参照
private static Object object = new Object();
@Override
public void run() {
while (length > 0) {
// 把需要同步的代码拆分到一个方法中,该方法的所有代码都需要多线程处理时
// 则这个代码需要加线程锁,可以声明该方法为线程锁方法
method();
}
}
/**
* 定义一个线程锁方法
* 当这个方法有可能被多个线程同时执行时,有可能出现线程问题,可以声明定义线程锁方法
* 当方法是对象方法时,该线程锁的锁对象为当前对象 this
* 当方法是静态方法时,该线程锁的锁对象为 MyThread.class 类
*/
private synchronized void method() {
System.out.println(getName() + ":HelloWorld" + length);
length--;
}
}
Lock 锁
对于 synchronized 线程锁已经为我们自动设定何时加锁何时解锁,但是我们并没有直接看到在哪里加上了锁,又在哪里释方了锁。
为了更清晰的表达如何加锁和释方锁,JDK5以后提供了一个新的锁对象Lock,Lock对象允许我们定义何时加锁,何时解锁。
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作,Lock中提供了获得锁和释放锁的方法
void lock();
获得锁
void unlock();
释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
Lock lock = new ReentrantLock();
创建一个ReentrantLock的实例。
public class MyThread extends Thread {
private static Integer length = 100;
// 创建一个Lock线程锁对象,可以手动设置何时加锁何时解锁
private static Lock lock = new ReentrantLock();
@Override
public void run() {
while (length > 0) {
// 为下面的代码加锁
lock.lock();
System.out.println(getName() + ":HelloWorld" + length);
length--;
// 执行完成后解锁
lock.unlock();
}
}
}
死锁
死锁是一种错误的做法,当线程锁被嵌套时,嵌套的线程锁就会有可能早就被锁住了,此时线程会卡住,如果互相锁住嵌套锁,则这些线程都因为在等待对放释放锁而无限等待,使得程序卡住。
public class MyDeadlock extends Thread{
Object objA = new Object();
Object objB = new Object();
@Override
public void run() {
while (true){
if ("线程A".equals(getName())){
synchronized (objA){
System.out.println("线程A拿到了A锁并锁住了");
synchronized (objB){
System.out.println("线程A拿到了B锁并锁住了");
}
}
}else if ("线程B".equals(getName())){
synchronized (objB){
System.out.println("线程B拿到了B锁并锁住了");
synchronized (objA){
System.out.println("线程B拿到了A锁并锁住了");
}
}
}
}
}
}wait()
等待唤醒机制
等待唤醒机制是指通过两个进程的等待和唤醒,使两个线程轮流被CPU执行处理。
抽象举例:消费者与生产者
消费者与生产者都是一个线程,具体关系如下:
消费者:
1.当启动时消费者发现生产者未生产完成时,进入等待wait()
,使CPU转到生产者处执行
2.当启动消费者发现生产者已完成生产时,直接使用。
3.当消费者使用完成后,通过唤醒notify() 或 notifyAll()
其它生产者线程继续生产
生产者:
1.当启动时生产者发现没有生产时,随即马上生产
2.当启动时生产者发现已存在生产时,进入等待wait()
,待消费者完成消费后,消费者唤醒notify() 或 notifyAll()
后再次生产
3.当生产完成后,通过唤醒notify() 或 notifyAll()
消费者进行消费。
日常举例:吃货与厨师
吃货:
1.当启动时CPU先执行吃货,若吃货发现没有食品,则进入等待状态wait()
,使CPU执行厨师
2.当启动时CPU先执行吃货,若吃货发现已存在食品,则进行消费,完成后唤醒notify() 或 notifyAll()
厨师
厨师:
1.当启动时CPU先执行厨师,若厨师发现没有食品,则进行生产,生产完成后,通过唤醒notify() 或 notifyAll()
吃货。
2.当启动时CPU先执行厨师,若厨师发现已存在食品,则进入等待状态wait()
,使CPU执行吃货。
案例代码:
桌子类:
Desk.java
/**
* 桌子类
* 用于管理【吃货】与【厨师】之间的线程调度
* 当桌子上有食物时,应让CPU执行吃货线程以消费
* 当桌子上没有食物时,应让CPU执行厨师线程以生产
* 来达到 A->B->A->B... 这样的线程执行规律
*/
public class Desk {
/**
* 定义一个Lock对象
*/
public static Object lock = new Object();
/**
* 是一个标志
* 它用于标记当前轮到那个线程来执行
* 此处的代表是【是否有食物】.1=有食物,0=没有食物
* 1=有食物:意味着下一次将引导CPU调用吃货线程进行消费
* 0=没有食物:意味着下一次将引导CPU调用厨师线程进行生产
*/
public static Integer foodFlag = 0;
/**
* 定义线程执行次数
* 在【吃货与厨师】案例中,如果吃货吃,厨师煮,将无限执行下去
* 此处是用于定义其执行次数
* 设定吃货最多能吃10份,当吃货吃完10份后也不再吃,厨师也不再生产
*/
public static Integer count = 10;
}
吃货类:
public class Footie extends Thread {
@Override
public void run() {
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
/**
* 当吃货已经吃了10份了,停止吃
*/
break;
} else {
/**
* 当吃货未吃完10份
* 1.判断桌上是否有食物,如果没有,则等待
*/
if (Desk.foodFlag == 0) {
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
/**
* 2.判断桌上是否有食物,如果有,则消费
*/
System.out.println("吃货正在吃一份食物");
/**
* 3.吃货已完成一份食物
*/
Desk.count--;
/**
* 4.完成后桌上为没有食物状态
*/
Desk.foodFlag = 0;
/**
* 5.唤醒厨师进行生产
*/
Desk.lock.notifyAll();
}
}
}
}
}
}
厨师类:
public class Cook extends Thread {
@Override
public void run() {
/**
* 1.循环
* 2.同步代码块
* 3.判断共享数据是否到了末尾(到了末尾)
* 4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
*/
while (true) {
synchronized (Desk.lock) {
if (Desk.count == 0) {
/**
* 当吃货已吃完10份了,不再生产
*/
break;
} else {
/**
* 当吃货未吒完10份时
* 1.判断桌上是否有食物,如果有,就等待
*/
if (Desk.foodFlag == 1) {
try {
Desk.lock.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
/**
* 2.判断桌上是否有食物,如果没有,就生产
*/
System.out.println("厨师生产了一份食物");
/**
* 3.生产完成后,设定该桌上已有食物
*/
Desk.foodFlag = 1;
/**
* 4.唤醒所有线程(吃货)进行消费
*/
Desk.lock.notifyAll();
}
}
}
}
}
}
启动线程:
Cook c = new Cook();
Footie f = new Footie();
c.start();
f.start();
阻塞队列实现等待唤醒机制
我们知道等待唤醒机制即是为了实现 A->B->A->B->A 这样的轮流执行方法。
我们可以使用队列来实现等待唤醒机制,即厨师可以在允许的数量范围内生产食物,而吃货也会在允许的数量范围内消费食物,厨师先生产的食物,会先被吃货消费
ArrayBlockingQueue
数组阻塞队列,用数组的形式对生产者生产的任务进行存储,消费者会以先生产先消费的方式对任务进行一一消费。
数组阻塞队列底层是数组,有界限。
厨师:
public class Cook extends Thread {
/**
* 定义一个队列
* 因为厨师需要取得这个队列,并把生产的食物存放到队列中去
*/
private ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue queue){
this.queue = queue;
}
@Override
public void run() {
/**
* 厨师只管生产食物即可
* 当厨师生产的食物已存满队列时,厨师才会进入等待状态
*/
while (true){
try {
/**
* 厨师生产食物并存入队列中
*/
queue.put("食物");
System.out.println("厨师生产了一份食物");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
吃货:
public class Footie extends Thread {
/**
* 定义一个队列
* 因为吃货需要取得这个队列,并取得队列中的食物进行消费
*/
ArrayBlockingQueue<String> queue;
public Footie(ArrayBlockingQueue queue) {
this.queue = queue;
}
@Override
public void run() {
while (true) {
try {
/**
* 在队列中取得一个食物
*/
String take = queue.take();
System.out.println("吃货正在吃" + take);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
启动线程:
/**
* 创建一个数组阻塞队列,其队列中最多只能保存10个生产
* 1.生产者在同一时间可以最多生产10个任务存在数组中,在不足10个任务以内时生产者无需等待消费者消费完后再进行生产
* 2.消费者可不需等待生产者生产,只要队列中存在任务,就可以取出队列中的任务进行消费
*/
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(10);
Cook c = new Cook(queue);
Footie f = new Footie(queue);
c.start();
f.start();
注部:在使用队列put
和take
时,应尽量避免包裹synchronized
,因为ArrayBlockingQueue
内部已经对put
和take
进行了线程锁的处理。
LinkedBlockingQueue
链表阻塞队列用数组的形式对生产者生产的任务进行存储,消费者会以先生产先消费的方式对任务进行一一消费。
线程的状态
新建状态(NEW) -- > 创建线程对象
就绪状态(NRUNNABLE) -- > start方法运行后,到抢到CPU执行权之前的状态
阻塞状态(BLOCKED) -- > 无法获得锁对象,如遇到 synchronized 线程且无法进入时
等待状态(WAITING) -- > wait方法,除非被其它线程进行notify唤醒,否则会一直等待
计时状态(TIMED_WAITING) -- > sleep方法,时间结束后,会再次进入就绪状态进行抢CPU执行权
结束状态(TERMINATED) -- > run方法全部代码运行完毕
注意:在JVM中,是没有【运行】状态的,因为当线程抢到了CPU执行权时,JVM会交给系统管理,此时的状态是在系统上,非JVM上了。
线程池
当我们创建一个线程并执行代码后,系统会把执行完成的线程当成垃执,被回收。
如果应用场境中,应用的运行过程中需要时常使用线程,而每一次的使用都重新创建一个线程,使用完后都被销毁,这种做法是非常浪费资源的。
Java 提供了一种线程池技术,它将生成一个容器,用于存储空闲的线程资源,当应用第一次使用线程时,会创建线程并使用,但使用完成后并不会把线程销毁,而是把这个线程交给容器中存储,当应用需要再次使用线程时,不需要创建线程,只需要在线程池中取得空闲线程使用即何,而这个工作都由Java底层完成。
创建线程池
Executors 工具类
线程池的工具类通过调用方法返回不同类型的线程对象
/**
* 创建一个没有上限的线程池,其上限的线程数为int的最大值
*/
public static ExecutorService newCachedThreadPool();
=> Executors.newCachedThreadPool(); // 创建一个无限线程池
/**
* 创建一个自定义上限的线程池
*/
public static ExecutorService newFixedThreadPool(int nThreads)
=> Executors.newFixedThreadPool(3); // 创建一个线程数为3的线程池
提交任务
创建完成后,可以通过 submit()
方法提交多线程任务到线程池中,线程池底层会自动为我们创建好线程并开始执行。
当执行完成后,线程会被线程池回收。当我们再次需要执行代码时,只需要再次submit()
提交任务即可。
当创建一个有限线程池时,若在该时刻有多个任务,但线程池中线程不足且线程池已满时,多出的任务只能进入等待状态,待线程池中有空闲线程时才会进入执行。
// 提交一个 Callable 多线程任务
submit(Callable<T> task)
// 提交一个带返回值的 Callable 多线程任务
submit(Callable<T> task, T result)
// 提交一个 Runnable 多线程任务
submit(Runnable task)
// 提交一个带返回值的 Runnable 多线程任务
submit(Runnable task, T result)
销毁线程池
调用 shutdown()
方法,即可销毁线程池,销毁后,线程池内的所有线程都被销毁。
自定义线程池
田Java提供的Executors工具类所提供的线程池非常好用,但缺少了一些自定义的能力,实际上Executors所创建的线程池,是来自于自定义线程池的封装。
创建自定义线程池
使用 ThreadPoolExecutor
类创建一个自定义线程池,它的构造函数中包含7个参数控制,它们分别如下
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
corePoolSize
: 创建的核心线程数。
说明:线程池在线程数控制上包含【核心线程数】和【临时线程数】概念,核心线程数会长期储存在线程池中,而临时线程数则不会长期储存。
maximumPoolSize
: 总的线程数,必须大于等于核心线程数。
说明:由corePoolSize
参数可知,线程池是包含【核心线程数】和【临时线程数】,因此如果总的线程数大于核心线程数,则剩下的线程则为临时线程
keepAliveTime
: 临时线程的最长存活时间值,不能少于0。
说明:临时线程不会长期存放在线程池当中,如果临时线程超过存活时间后,临时线程会被销毁。
unit
: 临时线程的最长存活时间单位,与keepAliveTime
合用,keepAliveTime
表示值,而unit
为单位(如秒),由TimeUnit
提供值
workQueue
: 用于存放线程的容器队列。
说明:存放线程的容器队列,可以是ArrayBlockingQueue
或LinkedBlockingQueue
,当出现任务需要等待处理时,任务将暂存在队列中
threadFactory
:创建线程工厂
说明:初始化线程工厂需要。
handler
:任务的拒绝策略
说明:当线程池出现过多任务等待时,会有一个拒绝服务的机制,相关拒绝服务机制请看下文拒绝策略。
拒绝策略
当任务数远远超出自定义线程所设定的容量后,会对多出的任务进行拒绝处理,有以下几种情况:
ThreadPoolExecutor.AbortPolicy // 默认策略:丢弃任务并抛出RejectedExecutionException异常
ThreadPoolExecutor.DiscardPolicy // 丢弃任务,但是不抛出异常
ThreadPoolExecutor.DiscardOldestPolicy // 抛弃队列中等待最久的任务,然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy // 调用任务的 run() 方法绕过线程池直接执行
线程池任务运行原理
前置条件
核心线程数:3
最多线程数:6 (即临时线程数为3)
临时线程数存活时间值:60
临时线程数存活时间单位:秒
容器队列:3
任务的拒绝策略:丢弃抛出异常
情况一:有2个任务(核心线程未满)
1.线程池中派出2个核心线程执行业务
2.线程池中空闲1个核心线程
情况二:有5个任务(核心线程已满,队列未满)
1.线程池中派出3个核心线程执行业务
2.剩下的2个任务会存在于队列中等待执行。
情况三:有8个任务(核心线程和队列已满,临时线程未满)
1.线程池中派出3个核心线程执行业务【任务1、2、3】
2.取出3个任务存放在队列中等待执行【任务4、5、6】
3.创建2个临时线程执行业务【任务7、8】
特别注意:当核心线程被占用完后,并非创建临时线程处理后续任务,而是先填满队列空间,如果队列空间都填满时,才会创建临时线程处理最后的业务
情况四:有10个任务(核心、临时、队列都满)
1.线程池中派出3个核心线程执行业务【任务1、2、3】
2.取出3个任务存放在队列中等待执行【任务4、5、6】
3.创建2个临时线程执行业务【任务7、8、9】
4.【任务10】丢弃并抛出异常,拒绝执行。
线程数规则
对于定义线程数量时,应该以什么标准去定义多少条线程数为准。
CPU密集型运算
对于CPU密集型的运算,CPU的负荷会比较大,因此建议设置线程数最大为【最大并行数+1】.如【4核8线程】,建议设定线程数为【8+1】
I/O密集型运算
对于I/O密集型的运算,CPU的负荷会相对较少,硬盘读写会比较多,其中包含两个操作,一是CPU运算操作,二是硬盘读写操作。其中硬盘读写时,CPU基本在等待状态。
为了最优化读写与运算之间的契合度,可以按照以下公式进行计算应该设置的线程数。
建议线程数=最大并行数 * 期望CPU利用率 *(总运行时间【CPU计算时间+等待时间】 / CPU计算时间)
举例:4核8线程的CPU,期望使用100%运算
假如,CPU运算使用1秒,硬盘操作使用1秒,则最后建议线程池设定线程数为:
建议线程数 = 8 * 100% * ( (1+1) / 1 ) = 16
共有 0 条评论