Java 基础
Java 容器
Java 并发
设计模式
目录

线程机制

# 线程机制

# Executors

Executors 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。这里的异步是指多个任务的执行互不干扰,不需要进行同步操作。

主要有三种 Executors:

  • CachedThreadPool:线程池的线程数量不确定,但若有空闲线程可以复用,则会优先使用可复用的线程。若所有线程均在工作,又有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执行完毕后,将返回线程池进行复用;
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(
        0,  //设置为0,即核心线程数为空;
        Integer.MAX_VALUE, // 线程池允许创建的线程数;
        60L, TimeUnit.SECONDS, // 允许空闲线程保留时间 
        new SynchronousQueue<Runnable>() //当任务超过线程池设置线程数时,任务进入等待队列。
    );
}
1
2
3
4
5
6
7
8
package com.code.concurrent.example4;

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

public class Main {
    public static void main(String[] args) {
        ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(5000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println("Thread Name:" + Thread.currentThread().getName()  );
                }
            });
        }
        System.out.println("Active Thread Count:" + executorService.getActiveCount() );
        executorService.shutdown();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但CachedThreadPool的maximumPool是无界的。这意味着,如果主线程提交任务速度高于maximumPool中线程处理任务速度时,CachedThreadPool会不断创建新线程。极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源。

  • FixedThreadPool: newFixedThreadPool是Java中的一个线程池类,它是一个固定大小的线程池,线程的数量在创建线程池时就已经确定。线程池中的线程数量一旦被确定,就不会发生改变。在Java中,newFixedThreadPool()方法创建的线程池是一个固定大小的线程池,线程池中的线程数量是固定的,由构造函数传入的参数指定,而任务队列的大小则由内部的阻塞队列来决定。

在使用newFixedThreadPool()方法创建线程池时,它使用的是LinkedBlockingQueue,这是一个无界的阻塞队列,它的大小是没有限制的。因此,当任务提交到线程池时,如果线程池中的线程正在执行任务,那么新提交的任务将被放入LinkedBlockingQueue中等待执行,直到有可用的线程来执行任务。当队列已经满了时,新提交的任务将会被阻塞,直到有空闲线程来处理队列中的任务。

因此,使用newFixedThreadPool()方法创建线程池时,队列的大小实际上是无限制的,但是需要注意的是,如果任务提交速度过快,队列可能会无限制地增长,导致内存溢出等问题。因此,在实际使用中需要根据具体的场景来合理设置线程池的大小和任务队列的容量,以充分利用系统资源并保证系统的稳定性。

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads, //线程数
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>());
}
1
2
3
4
5

线程数固定:newFixedThreadPool创建时需要指定线程数量,线程数量不能动态调整,因此无法适应不同的系统负载。 队列长度有限:newFixedThreadPool使用阻塞队列存储任务,队列长度有限,如果队列已满,新的任务将无法提交,可能会导致任务丢失。 任务执行时间不可控:newFixedThreadPool无法控制任务执行的时间,如果任务执行时间过长,会导致其他任务等待的时间过长,降低程序的效率和性能。

  • SingleThreadExecutor:相当于大小为 1 的 FixedThreadPool。

# ThreadPoolExecutor 执行过程

  • 1)当前线程< coolPoolSize时,直接创建线程执行

当一个任务通过 submit 或者 execute 方法提交到线程池的时候,如果当前池中线程数(包括闲置线程)小于 coolPoolSize,则创建一个线程执行该任务。

  • 2)当前线程>= coolPoolSize时,入队

如果当前线程池中线程数已经达到 coolPoolSize,则将任务放入等待队列。

    1. 当前线程>= coolPoolSize,但小于maximumPoolSize时,创建非核心线程执行

如果任务不能入队,说明等待队列已满,若当前池中线程数小于 maximumPoolSize,则创建一个临时线程(非核心线程)执行该任务。

  • 4)如果当前池中线程数已经等于 maximumPoolSize,此时无法执行该任务,根据拒绝执行策略处理。

注意:当池中线程数大于 coolPoolSize,超过 keepAliveTime 时间的闲置线程会被回收掉。回收的是非核心线程,核心线程一般是不会回收的。

如果设置 allowCoreThreadTimeOut(true),则核心线程在闲置 keepAliveTime 时间后也会被回收。

任务队列是一个阻塞队列,线程执行完任务后会去队列取任务来执行,如果队列为空,线程就会阻塞,直到取到任务。

  • 4种拒绝策略RejectedExecutionHandler:

  • AbortPolicy:丢弃任务,抛出运行时异常

  • CallerRunsPolicy:由提交任务的线程来执行任务

  • DiscardPolicy:丢弃这个任务,但是不抛异常

  • DiscardOldestPolicy:从队列中剔除最先进入队列的任务,然后再次提交任务

package com.code.concurrent.example4;

import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

public class Main2 {
    public static void main(String[] args) {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2,       // 核心线程数
                5,                  // 最大线程数
                30,                 // 保持存活时间
                TimeUnit.SECONDS,   // 时间单位
                new LinkedBlockingQueue<>(10), // 任务队列
                new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
        );
        
        for (int i = 0; i < 20; i++) {
            executor.execute(() -> {
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("Task executed on thread: " + Thread.currentThread().getName());
            });
        }
        executor.shutdown();
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# Daemon

守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。

当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。

main() 属于非守护线程。

在线程启动之前使用 setDaemon() 方法可以将一个线程设置为守护线程。

public static void main(String[] args) {
    Thread thread = new Thread(new MyRunnable());
    thread.setDaemon(true);
}
1
2
3
4

# sleep()

Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒。

sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。

public void run() {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
1
2
3
4
5
6
7

# yield()

对静态方法 Thread.yield() 的调用声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。

public void run() {
    Thread.yield();
}
1
2
3