进程和线程是计算机程序执行的两个重要概念。
1.进程: 进程是操作系统分配资源的基本单位,每个进程都有自己独立的地址空间,每启动一个进程,系统就会为它分配内存。进程间通信比较复杂,需要用到IPC(InterProcess Communication,进程间通信)机制。
进程有五种状态:创建、就绪、运行、阻塞和终止。
2.线程: 线程是进程中的一个执行单元,一个进程可以包含多个线程,它们共享进程的地址空间和资源。线程间通信比进程间通信要简单很多,因为它们可以直接读写进程数据段(如全局变量)来进行通信。
线程也有五种状态:创建、就绪、运行、阻塞和终止。
3.进程和线程的区别:
-
独立性:进程间的内存空间是独立的,而线程共享进程的内存空间。
-
资源消耗:创建或销毁进程时,系统性能开销明显,而线程的资源消耗远小于进程。
-
通信方式:进程间通信需要使用IPC机制,而线程可以直接读写全局变量进行通信。
-
影响:一个进程崩溃后,其他进程不受影响;而一个线程崩溃,会导致整个进程崩溃。
4.多线程和多进程的选择: 在需要进行频繁通信的情况下,多线程更有优势,因为它们可以直接读写内存进行通信。而在需要大量计算且相互独立的情况下,多进程可能更合适,因为它们不会因为一个进程的崩溃而影响其他进程。
并发和并行
并发:在同一时刻,有多个指令在单个CPU上交替执行。
并行:在同一时刻,有多个指令在多个CPU上同时执行。
实现多线程方式一:继承Thread类
方法介绍
方法名 | 说明 |
---|---|
void run() | 在线程开启后,此方法将被调用执行 |
void start() | 使此线程开始执行,Java虚拟机会调用run方法() |
实现步骤
-
定义一个类MyThread继承Thread类
-
在MyThread类中重写run()方法
-
创建MyThread类的对象
-
启动线程
-
代码演示
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName()+"Hello World");
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
多线程第一种启动方式
1.自己定义一个类继承Thread
2.重写run方法
3.创建子类的对象,并启动线程
*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.setName("线程一");
t2.setName("线程二");
//开启线程
t1.start();
}
}
实现多线程方式二:实现Runnable接口
Thread构造方法
方法名 | 说明 |
---|---|
Thread(Runnable target) | 分配一个新的Thread对象 |
Thread(Runnable target, String name) | 分配一个新的Thread对象 |
实现步骤
-
定义一个类MyRunnable实现Runnable接口
-
在MyRunnable类中重写run()方法
-
创建MyRunnable类的对象
-
创建Thread类的对象,把MyRunnable对象作为构造方法的参数
-
启动线程
public class MyRun implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
//获取到当前线程的对象
Thread t = Thread.currentThread();
System.out.println(t.getName()+"Hello World");
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
多线程第二种启动方式
1.自己定义一个类继承Runnable接口
2.重写run方法
3.创建自己类的对象
4.创建一个Thread类的对象,并开启线程
*/
//创建MyRun的对象
//表示多线程要执行的任务
MyRun mr = new MyRun();
//创建线程对象
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.setName("线程一");
t2.setName("线程二");
//开启线程
t1.start();
}
}
实现多线程方式三: 实现Callable接口
方法介绍
方法名 | 说明 |
---|---|
V call() | 计算结果,如果无法计算结果,则抛出一个异常 |
FutureTask(Callable<V> callable) | 创建一个 FutureTask,一旦运行就执行给定的 Callable |
V get() | 如有必要,等待计算完成,然后获取其结果 |
实现步骤
-
定义一个类MyCallable实现Callable接口
-
在MyCallable类中重写call()方法
-
创建MyCallable类的对象
-
创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
-
创建Thread类的对象,把FutureTask对象作为构造方法的参数
-
启动线程
-
再调用get方法,就可以获取线程结束之后的结果。
代码演示
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum = sum + i;
}
return sum;
}
}
public class ThreadDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
/*
多线程的第三种实现方式
特点:可以获取到多线程运行到的结果
1.创建一个类MyCallable实现Callable接口
2.重写call(有返回值,表示多线程运行的结果)
3.创建MyCallable的对象(表示多线程要执行的任务)
4.创建FutureTask的对象(作用管理多线程运行的结果)
5.创建Thread类的对象,并启动
*/
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask(mc);
Thread thread = new Thread(ft);
thread.start();
//获取多线程运行的结果
Integer result = ft.get();
System.out.println(result);
}
}
Java中多线程的三种主要实现方式各有其特点和适用场景。具体来说:
- 继承Thread类:通过继承Thread类并重写run方法来实现多线程,这种方法简单直观,但在Java的单继承体系下,可能会导致类的扩展性受限。
- 实现Runnable接口:通过实现Runnable接口并将该实现类的实例传递给Thread对象来创建线程,这种方式避免了单继承的限制,提高了代码的可读性和可维护性。
- 实现Callable接口:与Runnable类似,但可以返回执行结果,通常与FutureTask结合使用,允许线程执行完毕后获取返回值,增加了线程的使用场景。
总的来说,如果需要简单的线程逻辑并且不需要额外的返回结果,可以选择继承Thread类或实现Runnable接口。而当需要更复杂的线程管理和控制,或者希望利用线程池等高级特性时,应该考虑使用Executor框架。
线程休眠
相关方法
方法名 | 说明 |
---|---|
static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 |
代码演示
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "---" + i);
}
}
}
public class Demo {
public static void main(String[] args) throws InterruptedException {
/*System.out.println("睡觉前");
Thread.sleep(3000);
System.out.println("睡醒了");*/
MyRunnable mr = new MyRunnable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
t1.start();
t2.start();
}
}
线程优先级
线程调度
在Java中,除了上述两种基本的调度方式,还可以通过编程手段对线程的执行顺序进行一定程度的控制。例如,可以在需要等待或者后执行的线程中加入sleep()
方法,使其在进入CPU调度初期就进入休眠状态,从而给其他线程执行的机会。
优先级相关方法代码演示
-
线程调度是指在多线程环境中,操作系统或运行时环境如何分配CPU资源给各个线程的过程。
线程调度主要有两种调度方式:
- 分时调度:这种调度方式下,CPU的时间被平均分配给所有线程。每个线程轮流获得CPU的使用权,执行一段时间后再切换到下一个线程。这种方式简单公平,但可能不是最高效的方式,因为它不考虑线程的优先级和实际需求。
- 抢占式调度:这种调度方式会根据线程的优先级来决定哪个线程获得CPU的使用权。优先级高的线程会得到更多的执行机会。如果多个线程的优先级相同,通常会随机选择一个线程来执行。Java使用的就是抢占式调度,这种方式能够更好地满足高优先级任务的需求。
方法名 | 说明 |
---|---|
final int getPriority() | 返回此线程的优先级 |
final void setPriority(int newPriority) | 更改此线程的优先级线程默认优先级是5;线程优先级的范围是:1-10 |
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "---" + i);
}
return "线程执行完毕了";
}
}
public class Demo {
public static void main(String[] args) {
//优先级: 1 - 10 默认值:5
MyCallable mc = new MyCallable();
FutureTask<String> ft = new FutureTask<>(mc);
Thread t1 = new Thread(ft);
t1.setName("飞机");
t1.setPriority(10);
//System.out.println(t1.getPriority());//5
t1.start();
MyCallable mc2 = new MyCallable();
FutureTask<String> ft2 = new FutureTask<>(mc2);
Thread t2 = new Thread(ft2);
t2.setName("坦克");
t2.setPriority(1);
//System.out.println(t2.getPriority());//5
t2.start();
}
守护线程
相关方法代码演示
方法名 | 说明 |
---|---|
void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 |
public class MyThread1 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(getName() + "---" + i);
}
}
}
public class MyThread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "---" + i);
}
}
}
public class Demo {
public static void main(String[] args) {
MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();
t1.setName("女神");
t2.setName("备胎");
//把第二个线程设置为守护线程
//当普通线程执行完之后,那么守护线程也没有继续运行下去的必要了.
t2.setDaemon(true);
t1.start();
t2.start();
}
}
线程的生命周期
线程同步
卖票
案例需求
电影院卖100张票,
而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
实现步骤
public class SellTicket extends Thread {
static int ticket = 0;
@Override
public void run() {
while (true) {
if (ticket < 100) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName() + "正在卖" + ticket + "票");
} else {
break;
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
*/
SellTicket s1 = new SellTicket();
SellTicket s2 = new SellTicket();
SellTicket s3 = new SellTicket();
s1.setName("窗口一");
s2.setName("窗口二");
s3.setName("窗口三");
s1.start();
s2.start();
s3.start();
}
}
卖票案例的问题
-
卖票出现了问题
-
相同的票出现了多次
-
出现了多的票
-
-
问题产生原因
线程执行的随机性导致的,可能在卖票过程中丢失cpu的执行权,导致出现问题
同步代码块解决数据安全问题
-
安全问题出现的条件
-
是多线程环境
-
有共享数据
-
有多条语句操作共享数据
-
-
如何解决多线程安全问题呢?
-
基本思想:让程序没有安全问题的环境
-
-
怎么实现呢?
-
把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可
-
Java提供了同步代码块的方式来解决
-
同步代码块格式:
synchronized(任意对象) {
多条语句操作共享数据的代码
}
synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁
同步的好处和弊端
-
好处:解决了多线程的数据安全问题
-
弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率
代码演示
public class SellTicket extends Thread {
static int ticket = 0;
//锁对象,一定是唯一的
@Override
public void run() {
synchronized (SellTicket.class){
while (true) {
if (ticket < 100) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
ticket++;
System.out.println(getName() + "正在卖" + ticket + "票");
} else {
break;
}
}
}
}
}
同步方法解决数据安全问题
同步方法的格式
同步方法:就是把synchronized关键字加到方法上
修饰符 static synchronized 返回值类型 方法名(方法参数) {
方法体;
}
同步静态方法的锁对象是什么呢?
类名.class
非静态:this
同步静态方法的锁对象是当前类的Class对象。
在Java中,当使用synchronized
关键字修饰一个静态方法时,这意味着这个方法被同步化,并且所有线程在访问这个方法时必须先获得锁。对于静态同步方法,这个锁就是当前类的Class对象。这与其他非静态同步方法使用的锁不同,后者使用的是实例对象作为锁。
具体来说,当一个线程尝试访问一个类的静态同步方法时,它会尝试获取该类对应的Class对象的锁。如果该锁已被其他线程持有,则当前线程必须等待直到锁被释放。这种机制确保了同一时刻只有一个线程能够执行同一个类的任何静态同步方法。
此外,静态同步方法与非静态同步方法之间不会发生竞态条件,因为它们使用的是不同的锁对象。静态同步方法使用的是类对象本身的锁,而非静态同步方法使用的是实例对象的锁。
了解同步静态方法的锁对象对于编写多线程程序和理解Java内存模型中的线程安全和并发控制非常重要。
Lock锁
Lock锁是Java并发编程中的一种同步机制,它提供了比synchronized关键字更加灵活的锁操作。
首先,Lock锁的优势在于它能够提供更广泛的锁操作。与synchronized相比,Lock锁可以实现更细粒度的锁控制,例如可重入锁、公平锁等。具体来说:
- 可重入锁:允许同一个线程多次获得锁,而不会导致自己被阻塞。
- 公平锁:保证等待时间最长的线程能够先获得锁,避免线程饥饿现象。
- 读写锁:允许多个读线程同时访问,但在写线程访问时会独占锁。
此外,Lock锁的使用通常需要手动释放,这要求开发者在finally块中释放锁以确保资源的正确释放。而synchronized则不需要手动释放。
总的来说,Lock锁提供了更多的功能和灵活性,但也带来了更高的复杂性。在使用Lock锁时,需要注意正确管理锁的获取和释放,以避免死锁或资源泄露。
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock构造方法
方法名 | 说明 |
---|---|
ReentrantLock() | 创建一个ReentrantLock的实例 |
加锁解锁方法
方法名 | 说明 |
---|---|
void lock() | 获得锁 |
void unlock() | 释放锁 |
代码演示
public class Ticket implements Runnable {
//票的数量
private int ticket = 100;
private Object obj = new Object();
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
//synchronized (obj){//多个线程必须使用同一把锁.
try {
lock.lock();
if (ticket <= 0) {
//卖完了
break;
} else {
Thread.sleep(100);
ticket--;
System.out.println(Thread.currentThread().getName() + "在卖票,还剩下" + ticket + "张票");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
// }
}
}
}
死锁
死锁是指两个或多个执行单元(例如进程、线程或事务)在等待彼此持有的资源时,导致它们都无法 progress。产生死锁的情况通常涉及以下四个方面:
- 互斥条件:指资源至少由一个执行单元持有,且在释放之前其他执行单元无法使用。
- 占有和等待条件:指执行单元已经持有至少一个资源,但又提出了新的资源请求,被阻塞的执行单元仍然保持着它的资源。
- 不剥夺条件:指一个执行单元获得的资源在未使用完之前不能被强行剥夺,即使该资源有可能满足其他等待资源的执行单元的需求。
- 循环等待条件:存在一种环形链,每个执行单元都在等待下一个执行单元所占有的资源。
在实际开发中,为了避免死锁,可以采取一些策略,如尽量减少事务的大小和持续时间,避免长时间占用锁资源,以及尽量保持事务之间访问资源的一致性和顺序,避免交叉锁定。
下面是可以产生死锁的一段代码:~
public class DeadlockDemo {
private static Object lock1 = new Object();
private static Object lock2 = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (lock1) {
System.out.println("Thread 1: Holding lock 1...");
try {
Thread.sleep(100); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1: Waiting for lock 2...");
synchronized (lock2) {
System.out.println("Thread 1: Holding lock 1 & 2...");
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock2) {
System.out.println("Thread 2: Holding lock 2...");
try {
Thread.sleep(100); // 模拟耗时操作
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 2: Waiting for lock 1...");
synchronized (lock1) {
System.out.println("Thread 2: Holding lock 1 & 2...");
}
}
});
thread1.start();
thread2.start();
}
}
生产者消费者
概述
生产者消费者模式是一种经典的多线程协作模型,它涉及两类线程:生产者和消费者。
生产者:负责生产数据或对象,并将它们放入一个共享的队列中。生产者不直接与消费者交互,而是将产品放入队列后继续生产,这样可以保证生产者的效率不会因为等待消费者而降低。 消费者:负责从队列中取出数据或对象并进行消费。消费者不需要等待生产者生产,只需检查队列中是否有产品可消费。这样,消费者可以保持持续的工作状态,提高整体的处理效率。
此外,在实现生产者消费者模式时,通常需要考虑以下几个关键点:
- 共享队列:生产者和消费者通过这个队列进行间接通信。生产者将产品放入队列,消费者从队列中取出产品。
- 同步机制:需要确保当队列满时,生产者停止生产;当队列空时,消费者停止消费。这通常通过使用信号量、锁或其他同步机制来实现。
- 线程通信:在某些情况下,生产者生产了新的产品后,需要通知消费者来消费。这种通信可以通过条件变量或其他同步工具来实现。
总的来说,生产者消费者模式有效地解决了生产者和消费者之间的速度匹配问题,允许两者以不同的速度运行,同时确保数据的安全传输和处理。
方法名 | 说明 |
---|---|
void wait() | 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法 |
void notify() | 唤醒正在等待对象监视器的单个线程 |
void notifyAll() | 唤醒正在等待对象监视器的所有线程 |
为方便理解采用以下示例
来源://https://www.bilibili.com/video/BV17F411T7Ao/
桌子
public class Desk {
/*
作用:控制生产者和消费者的执行
*/
//是否有面条 0:没有 1:有面条
public static int foodFlag = 0;
//总个数
public static int count = 10;
//锁对象
public static Object lock = new Object();
}
生产者
public class Cook extends Thread{
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else {
//判断桌子上是否有食物
if(Desk.foodFlag == 1){
//如果有,就等待
try {
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
//如果没有,就制作食物
System.out.println("厨师做了一碗面条");
//修改桌子上的食物状态
Desk.foodFlag = 1;
//叫醒等待的消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
消费者
public class Foodie extends Thread{
@Override
public void run() {
while(true){
synchronized (Desk.lock){
if(Desk.count == 0){
break;
}else {
if(Desk.foodFlag == 0){
//用锁的对象调用wait()
try {
//让当前线程跟锁进行绑定
Desk.lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
Desk.count--;
//如果有,就开吃
System.out.println("吃货正在吃面条,还能再吃"+Desk.count+"碗!!!");
//吃完之后,唤醒厨师继续做
Desk.lock.notify();
Desk.foodFlag = 0;
}
}
}
}
}
}
二:阻塞队列完成等待唤醒机制
生产者
public class Cook extends Thread{
ArrayBlockingQueue<String> queue;
public Cook(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
//不断的把面条放在阻塞队列当中
try {
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Foodie extends Thread{
ArrayBlockingQueue<String> queue;
public Foodie(ArrayBlockingQueue<String> queue) {
this.queue = queue;
}
@Override
public void run() {
while(true){
//不断从阻塞队列中获取面条
try {
String food = queue.take();
System.out.println(food);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
/*
利用阻塞队列完成生产者消费者(等待唤醒机制)
//细节:生产者消费者必须使用同一个阻塞队列
*/
//1.创建阻塞队列的对象
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(1);
//创建线程的对象,并把阻塞队列传递过去
Cook c = new Cook(queue);
Foodie f = new Foodie(queue);
c.start();
f.start();
}
}
线程状态
线程池
线程池是一种用于管理线程的资源池,它可以显著提高多线程程序的性能和可靠性。
线程池的实现基于生产者-消费者模式,任务提交方作为生产者将任务放入线程池,而线程池中的工作线程作为消费者来执行这些任务。以下是线程池的一些关键特点:
- 线程复用:线程池通过重用现有线程来执行新任务,减少了频繁创建和销毁线程所带来的开销。
- 任务管理:线程池内部通常有一个工作队列,用于存储等待执行的任务,这样可以实现任务的有序执行。
- 性能提升:通过减少线程创建的开销和优化线程调度,线程池能够提高程序的响应速度和吞吐量。
- 资源控制:线程池可以限制同时运行的线程数量,防止系统过载,同时也可以减少上下文切换的开销。
- 编程简化:使用线程池可以简化多线程编程,开发者只需关注任务的实现,而不必处理线程的创建和管理细节。
- 异步执行:线程池支持异步执行任务,可以在不阻塞主线程的情况下执行耗时操作,提升用户体验。
- 灵活性:现代编程语言如C++提供了丰富的API和库来支持线程池的实现,使得开发者可以根据需要定制线程池的行为。
- 并发控制:线程池可以帮助开发者更好地控制并发级别,避免资源竞争和死锁等问题。
综上所述,线程池是一种高效的线程管理机制,它通过池化技术优化了线程的创建、管理和调度,为并发编程提供了强大支持。在实际应用中,合理地使用线程池可以带来显著的性能提升和更好的资源利用率。
线程池代码实现
-
创建线程池
-
提交任务
-
所有的任务全部执行完毕,关闭线程池
线程池-Executors默认线程池
我们可以使用Executors中所提供的静态方法来创建线程池
static ExecutorService newCachedThreadPool() 创建一个默认的线程池 static newFixedThreadPool(int nThreads) 创建一个指定最多线程数量的线程池
public class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println(Thread.currentThread().getName()+"---"+ i);
}
}
}
public class test8 {
public static void main(String[] args) {
//获取线程池对象
ExecutorService pool1 = Executors.newCachedThreadPool();
//ExecutorService pool1 = Executors.newCachedThreadPool(3);
//提交任务
pool1.submit(new MyRunnable());
//销毁线程池
/* pool1.shutdown();*/
}
}
线程池-参数详解
Java中的线程池通过ThreadPoolExecutor
类实现,它提供了灵活的参数配置来满足不同场景的需求。以下是该类构造方法中的七个主要参数及其作用:
- corePoolSize:这是线程池的核心线程数量,即线程池中始终保持的最小线程数。即使这些线程处于空闲状态,也不会被销毁,除非设置了
allowCoreThreadTimeOut
属性。 - maximumPoolSize:这是线程池允许创建的最大线程数量。当任务数量超过核心线程数时,线程池会尝试创建新的线程来处理任务,但总数不会超过这个参数指定的值。
- keepAliveTime:当线程池中的线程数量超过核心线程数时,多余的空闲线程在被销毁之前可以存活的时间。这个参数可以帮助控制资源使用,避免长时间持有不必要的线程资源。
- unit:与
keepAliveTime
参数配合使用,用于指定keepAliveTime
的时间单位,如TimeUnit.SECONDS
表示秒。 - workQueue:用于存放待执行任务的阻塞队列。不同的队列实现有不同的特性,如
ArrayBlockingQueue
、LinkedBlockingQueue
等,选择合适的队列对线程池的性能有重要影响。 - threadFactory:用于创建新线程的工厂。可以通过实现
ThreadFactory
接口来自定义线程的创建过程,如设置线程名称、守护状态等。 - handler:当线程池和队列都满了,无法接受新任务时的拒绝策略。常见的策略有
AbortPolicy
(默认,抛出异常)、CallerRunsPolicy
(调用者运行)和DiscardOldestPolicy
(丢弃最旧任务)等。
综上所述,合理配置这些参数对于提高线程池的效率和稳定性至关重要。例如,根据任务的特性和系统的资源状况来调整核心线程数和最大线程数,以及选择适合的拒绝策略,可以在保证性能的同时避免资源的过度消耗
为方便理解看下图 图源://黑马程序员Java零基础视频教程_上部(Java入门,含斯坦福大学练习题+力扣算法题和大厂java面试题)_哔哩哔哩_bilibili
public class test8 {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
3,//核心线程数量,不能小于0
6,//最大线程数,不能小于0,最大数量 >= 核心线程数量
60, //空闲线程最大存活时间
TimeUnit.SECONDS,//时间单位
new ArrayBlockingQueue<>(3),//任务队列Executors.defaultThreadFactory(),
Executors.defaultThreadFactory(),//创建线程工厂
new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略
);
}
}