前言
在拥有多核CPU的情况下,Java的线程可以被同时调度到不同的CPU上,同时执行;这是Python线程和Java线程的区别;
单线程相当于1个工人,多线程相当于雇佣了多个工人;
1.多线程的使用场景?
1.1.我们可以让多个工人一起处理一个相同的任务
2.2.我们也可以把多个工人分成2队,协作起来完成1个共同的任务;
以上是我们使用多线程的场景,在这2种使用场景下回遇到以下两种问题、
2.多线程模式下会引发哪些问题?
2.1.同一个争抢同一资源的问题的解决方案
使用同步代码块、锁可以解决同类线程争抢同一个资源的问题
2.2.如何保证执行不同线程任务协调完成同1个任务?
如果2个执行不同任务的线程 之间,要构成生产者消费者模式 ,就需要等待唤醒通知机制(消息队列);
这就又回到多个线程争抢同1个资源的问题上,所以锁是实现线程间通信的基础。
一、多线程实现方式
Java使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例;
每个线程其实就是指定一段代码, 让这段代码和其他线程并行执行;
我们可以通过3种方式创建线程。
1.继承Thread类
我们可以通过继承java.lang.Thread类,生成线程对象;
/1.让自定义的类继承java.lang.Thread类(该自定义的类具备了Thread类的特性,Java就会认可MyThread类的对象就是一条线程) public class MyThread extends Thread { //2.自定义线程的目的:让Java虚拟机开辟1个线程,和其他线程一起并行执行 //需要让自定义的线程类去重写Thread类中的run()方法:这个run()方法就是这个线程要执行的代码 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("MyThread的run方法循环了" + i + "次!"); } } }
------------------------------------------------------------------------------------------------
public class MyThreadTest { public static void main(String[] args) { //3.创建自定义的线程类对象 MyThread myThread = new MyThread(); //4.启动线程:myThread.run(); /*调用run()方法也可以运行代码√,但是没有开辟线程去运行,就是单纯地对象方法调用 */ myThread.start(); for (int i = 0; i < 100; i++) { System.out.println("Main线程的for循环了第" + (i + 1) + "次---------"); } } }
2.实现Runnable接口
实现Runnable接口完成了线程和线程任务的解耦,避免了单继承的局限性。
自定义类实现Runabke接口之后创建出来线程任务对象(弹夹);
将线程任务对象作为Thread类构造方法的参数创建线程对象(枪);
特点是:线程和线程任务解耦(分开)线程是线程,线程任务是线程任务;(枪和弹夹:枪是一把枪,弹夹可以随时更换)专注于线程任务。
继承Thread类和实现Runable接口实现多线程的区别?
- 适合多个相同程序代码的线程去共享同一资源
- 可以避免java中单继承的局限性
- 实现解耦
package com.itheima.runable; /* 1.让自定义的类实现Runnable线程任务接口; 当自定义的实现类实现了Runnable接口,该自定义的类就具备了作为线程任务的能力 */ public class MyRunableTask implements Runnable { //2.实现run()方法:指定线程要执行的任务代码 @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("MyRunableTask的run方法循环了第" + i + "次"); } } }
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
package com.itheima.runable; public class MyRunableTest { public static void main(String[] args) { //3.创建自定义的线程任务类对象(弹夹) MyRunableTask myRunableTask = new MyRunableTask(); //4.创建线程对象,将线程任务对象作为构造方法的参数(枪) Thread thread = new Thread(myRunableTask); //5.启动线程对象,调用start方法 thread.start(); } }
3.实现Calable接口
以上两种创建线程的方式,都需要有1个run(),run方法的不足之处就是无法有返回值也无法抛出异常;
如果想要获取线程任务的返回结果,可以通过实现实现Calable接口的方式创建线程对象。
package Threads; import java.util.Random; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; class TestThread03 implements Callable<Integer> { @Override public Integer call() throws Exception { return new Random().nextInt(100); } } public class CreateThread03 { public static void main(String[] args) throws Exception { //定义1个线程对象 TestThread03 tr = new TestThread03(); FutureTask ft = new FutureTask(tr); Thread t3 = new Thread(ft); t3.start(); //获取线程得到的返回值 Object obj = ft.get(); System.out.println(obj); } }
4.
@Override public IndexBaseInfoVO getBaseInfo(String beginCreateTime, String endCreateTime) { //1)构建一个空的结果集对象 IndexBaseInfoVO result = new IndexBaseInfoVO(); //2 封装结果集属性 String username = SecurityUtils.getUsername(); try { // 2.2 开始查询第一个属性 线索数量 CompletableFuture<Integer> clueNums = CompletableFuture.supplyAsync(() -> { return reportMpper.getCluesNum(beginCreateTime, endCreateTime, username); }); // 2.3 开始查询第一个属性 商机数量 CompletableFuture<Integer> bussinessNum = CompletableFuture.supplyAsync(() -> { return reportMpper.getBusinessNum(beginCreateTime, endCreateTime, username); }); // 2.4 开始查询第一个属性 合同数量 CompletableFuture<Integer> contractNum = CompletableFuture.supplyAsync(() -> { return reportMpper.getContractNum(beginCreateTime, endCreateTime, username); }); // 2.5 开始查询第一个属性 销售金额数量 CompletableFuture<Double> saleAmount = CompletableFuture.supplyAsync(() -> { return reportMpper.getSalesAmount(beginCreateTime, endCreateTime, username); }); // 3 join等待所有线程全部执行完成 CompletableFuture.allOf( clueNums, bussinessNum, contractNum, saleAmount ).join(); //4 封装结果集对象 result.setCluesNum(clueNums.get()); result.setBusinessNum(bussinessNum.get()); result.setContractNum(contractNum.get()); result.setSalesAmount(saleAmount.get()); } catch (Exception e) { e.printStackTrace(); return null; } //4 返回结果集对象 return result; }
5.线程的生命周期
二、线程的常用方法
线程创建完成之后,我们可以调用线程对象中封装的一些API对线程进行操作;
1.setPriority()
我们可以通过setPriority设置线程被CPU调度的优先级。
* The minimum priority that a thread can have. */ public final static int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public final static int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public final static int MAX_PRIORITY = 10;
正常优先级 为5,最小优先级为1,最大优先级为10;
package Threads; class Task01 extends Thread { @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("task01--" + i); } } } class Task02 extends Thread { @Override public void run() { for (int i = 20; i < 30; i++) { System.out.println("task02--" + i); } } } public class ThreadMthods { public static void main(String[] args) { Task01 t1 = new Task01(); //设置线程1的优先级别为1 t1.setPriority(1); t1.start(); //设置线程2的优先级别为10 Task02 t2 = new Task02(); t1.setPriority(10); t2.start(); } }
2.join()
当一个线程调用了join方法之后,这个线程就会优先被执行;
当它执行结束后,CPU才可以去执行其他线程;
注意:线程必须先执行start()再执行join(),才能生效;
package Threads; class Task03 extends Thread { Task03() { } Task03(String name) { super.setName(name); } @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println(this.getName() + i); } } } public class ThreadMthods { public static void main(String[] args) throws Exception { for (int i = 0; i < 100; i++) { if (i == 6) { Task03 t3 = new Task03("子线程"); t3.start(); t3.join(); //半路杀出个程咬金 } Thread.currentThread().setName("main线程"); System.out.println(Thread.currentThread().getName() + i); } } }
3.sleep()
通过Thread.sleep()静态方法可以使当前线程阻塞N毫秒;
/* 睡眠排序法: 给集合中每个需要排序的数字元素(N)都开启一个对应的线程睡N毫秒。 谁最后醒来谁就是老大? */ public class SleepSort { public static void main(String[] args) { int[] intArray = {3, 2, 9, 7}; for (int i : intArray) { new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(i); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(i); } }).start(); } } }
4.setDaemon(true)
在古代皇帝死了,他的妃子通常会哭的很惨,因为接下来她也要殉葬;
在Python中子线程默认就是主线程的守护线程,一旦主线程提前结束,子线程即使没有结束也要强制其结束;
而Java中的线程是跑在不同的CPU上的,主线程和子线程的地位是平等的;
在Java中,默认情况下即使主线程结束了,子线程也可以继续执行;
但是只要我们把子线程设置为守护线程,一旦主线程结束,子线程会立即结束;
package Threads; class Task extends Thread { @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("子线程" + i); } } } public class ThreadMthods { public static void main(String[] args) throws Exception { Task t1 = new Task(); t1.setDaemon(true);//注意先设置守护线程,在启动该线程 t1.start(); //在Python中子线程默认就是主线程的守护线程,一旦主线程提前与子线程结束,子线程即使没有结束也要强制其结束; //而Java的线程可以跑在不同的CPU上,主线程和子线程是平等的;默认情况下即使主线程结束了,子线程也可以继续执行; for (int i = 0; i < 10; i++) { System.out.println("主线程" + i); } } }
5.stop()
我们可以通过调用stop方法,来结束当前线程;
package Threads; public class ThreadMthods { public static void main(String[] args) throws Exception { for (int i = 1; i < 11; i++) { if (i == 7) { Thread.currentThread().stop(); } System.out.println("主线程" + i); } } }
三、线程安全问题-内部矛盾
我这里所说的线程安全问题即 N个执行相同任务的线程,争抢同1个资源;
例如:100个人在北京西站去抢10张北京到哈尔滨的火车票;
在Java中我们可以通过同步代码块、静态同步方法、显示锁解决这种问题;
1.synchronized ()同步代码块
package Threads; class TicketAgency extends Thread { static int tickets = 10; TicketAgency() { } TicketAgency(String name) { super.setName(name); } @Override public void run() { //确保每个线程对象,使用的都是同一把锁。不要使用this synchronized ("zhanggen") { for (int i = 0; i <= 100; i++) { if (tickets > 0) { System.out.printf("我在%s,抢到了北京到哈尔滨的第%d张票!\r\n", super.getName(), tickets--); } } } } } public class ThreadMthods { public static void main(String[] args) throws Exception { TicketAgency agency01 = new TicketAgency("窗口1"); agency01.start(); TicketAgency agency02 = new TicketAgency("窗口2"); agency02.start(); TicketAgency agency03 = new TicketAgency("窗口3"); agency03.start(); } }
2.静态同步方法
public static synchronized
package Threads; class TicketAgency extends Thread { static int tickets = 10; TicketAgency() { } TicketAgency(String name) { super.setName(name); } @Override public void run() { //必须确保多个线程对象,使用的都是同1把锁。不要使用this for (int i = 0; i <= 100; i++) { buyTicket(); } } //同步方法:增加static修饰符,确保锁住的不是this public static synchronized void buyTicket() { if (tickets > 0) { System.out.printf("我在%s,抢到了北京到哈尔滨的第%d张票!\r\n", Thread.currentThread().getName(), tickets--); } } } public class ThreadMthods { public static void main(String[] args) throws Exception { TicketAgency agency01 = new TicketAgency("窗口1"); agency01.start(); TicketAgency agency02 = new TicketAgency("窗口2"); agency02.start(); TicketAgency agency03 = new TicketAgency("窗口3"); agency03.start(); } }
3.Lock类
synchronized是Java中的关键字,这个关键字的识别是依靠JVM来识别完成的,是虚拟机级别的;
在JKD1.5之后我们可以通过API级别的Lock显示锁(自己lock+unlock)实现同步,这种方式更加灵活。
package Threads; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class TicketAgency extends Thread { static int tickets = 10; //拿来一把锁 Lock lock = new ReentrantLock(); //多态 接口=实现类 TicketAgency() { } TicketAgency(String name) { super.setName(name); } @Override public void run() { for (int i = 0; i <= 100; i++) { lock.lock(); if (tickets > 0) { System.out.printf("我在%s,抢到了北京到哈尔滨的第%d张票!\r\n", Thread.currentThread().getName(), tickets--); } lock.unlock(); } } } public class ThreadMthods { public static void main(String[] args) throws Exception { TicketAgency agency01 = new TicketAgency("窗口1"); agency01.start(); TicketAgency agency02 = new TicketAgency("窗口2"); agency02.start(); TicketAgency agency03 = new TicketAgency("窗口3"); agency03.start(); } }
四、线程通信问题
线程通信问题即生产者/消费者模型下,如何保证两端执行不同任务的多个线程协调一致,完成共同的任务;
1.生产者消费者模型
消费者:消费者吃包子
生产者和消费者模型的要求:
- 消费者没有包子可以消费时,等待生产者生产出包子再消费。
- 生产者生产出包子之后,通知生产者来消费,避免生产过剩。
2.死锁的产生
在生产者消费者模型中,当两个线程都持有对方线程所需要的锁,但本线程也不释放对方想要的锁时,两个线程全部进入死锁状态。
package com.itheima.lock; public class DeadLockDemo2 { //死锁的前提:有2把以上的锁 public static final Object LOCK_A = new Object(); public static final Object LOCK_B = new Object(); public static void main(String[] args) { //创建2个线程: new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "准备获取LOCK_A锁!"); synchronized (LOCK_A) { System.out.println(Thread.currentThread().getName() + "已经获取了LOCK_A锁,准备休眠100毫秒之后,再获取LOCK_B锁!"); try { //线程休眠期间:让出CPU执行权,但是还在不会释放锁。 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "休眠100毫秒完成,开始获取LOCK_B锁!"); synchronized (LOCK_B) { System.out.println(Thread.currentThread().getName() + "即获取了LOCK_A锁,也获取到了LOCK_B锁!"); } } } }, "线程A").start(); new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "准备获取LOCK_B锁"); synchronized (LOCK_B) { System.out.println(Thread.currentThread().getName() + "已经获取到了,LOCK_B锁,准备休眠100毫秒之后,再获取LOCK_A锁"); try { //线程休眠期间:让出CPU执行权,但是不会释放锁。 Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "休眠100毫秒完成,开始获取LOCK_A锁!"); synchronized (LOCK_A) { System.out.println(Thread.currentThread().getName() + "即获取了LOCK_B锁,也获取到了LOCK_A锁!"); } } } }, "线程B").start(); } } /* 死锁:两个线程都持有对方线程所需要的锁,但本线程也不释放对方想要的锁。 * 情况1:线程A先执行 * 线程A拿到第一把锁(LOCK_A) *线程A进入休眠状态让出CPU执行权,但没有释放LOCK_A锁。 * * 线程B开始执行 * 线程B获取到第一把锁(LOCK_B) * 线程B进入休眠状态让出CPU执行权,但没有释放LOCK_B锁。 * * 线程A休眠完成,开始获取第二把锁(LOCK_B),此时线程B还在休眠中无法释放LOCK_B; *线程B休眠完成,开始获取第二把锁(LOCK_A),此时线程A还在等待线程B放LOCK_A, * 线程A等待线程B只能等待线程 * */
3.等待唤醒机制(wait、notify、notifyAll )
Java的元类即Object类提供了3个方法,可以帮助我们实现等待唤醒机制,以解决死锁问题。
- notify()方法:随机唤醒1个进入无限等待状态的线程。
- notifyAll()方法:唤醒所有进入无限等待状态的线程。
注意:
- 以上的方法必须在同步代码块和同步方法中才能使用;
- 如果线程获得锁成功之后,该线程就会沿着wait()方法之后的代码继续执行。
- wait()和notify()必须放在synchronized代码块或者由synchronized修饰的同步方法内;
package com.itheima.pc2; import java.util.Objects; //包子类 public class Baozi { //标识包子的编号 private int count; //表示有没有包子的成员 private boolean flag; public Baozi() { } public Baozi(int count, boolean flag) { this.count = count; this.flag = flag; } public int getCount() { return count; } public void setCount(int count) { this.count = count; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Baozi)) return false; Baozi baoZi = (Baozi) o; return getCount() == baoZi.getCount() && isFlag() == baoZi.isFlag(); } @Override public int hashCode() { return Objects.hash(getCount(), isFlag()); } }
--------------------------------------------
package com.itheima.pc2; public class Producer implements Runnable { //将 private Baozi baozi; public Producer(Baozi baozi) { this.baozi = baozi; } @Override public void run() { // System.out.println("生产者线程启动了,包子共享对象是"+this.baozi); while (true) { //这里之所以选择Baozi作为锁对象是因为生产者和消费者拿到的是同一个包子 synchronized (baozi) { try { if (baozi.isFlag()) { //如果执行到这里,说明当前有包子,需要被消费者消费,那么生产者就不要生产了 baozi.wait(); } else { //执行到这来说明没有包子 baozi.setCount(baozi.getCount() + 1); System.out.println(Thread.currentThread().getName() + "生产了第" + baozi.getCount() + "个线程!"); baozi.setFlag(true); baozi.notifyAll();//唤醒消费者,前来消费 } } catch (InterruptedException e) { e.printStackTrace(); } } } } }
--------------------------------------------
package com.itheima.pc2; public class Consumer implements Runnable { private Baozi baozi; public Consumer(Baozi baozi) { this.baozi = baozi; } @Override public void run() { // System.out.println("消费线程启动了,包子共享对象是"+this.baozi); while (true) { //这里之所以选择Baozi作为锁对象是因为生产者和消费者拿到的是同一个包子 synchronized (baozi) { //判断是否有包子可以消费 try { if (!baozi.isFlag()) { //如果没有包子消费者进入无限等待状态 baozi.wait(); } else { //消费者需要消费包子 System.out.println("消费者消费了第" + baozi.getCount() + "个包子。"); baozi.setFlag(false); baozi.notifyAll(); } } catch (InterruptedException e) { e.printStackTrace(); } } } } }
--------------------------------------------
package com.itheima.pc2; public class Test { public static void main(String[] args) { //创建包子对象并且初始化 Baozi baozi = new Baozi(0, false); //创建消费者和生产者的消费任务,将同一个包子对象作为参数传递; Producer producer = new Producer(baozi); Consumer consumer = new Consumer(baozi); //创建生产者和消费者 new Thread(producer,"生产者线程").start(); new Thread(consumer,"消费者线程").start(); } }
4.await
以上我们只是完成1个生产者线程和1个消费者线程之间1对1的顺序通信;
在JDK1.5之后我们可以使用await()和signal(),实现多个消费者线程 和多个生产者线程之间,多对多通信的顺序通信;
package zhanggen.com; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; //商品类 public class Product { //品牌 private String band; //名字 private String name; //生产和消费指示灯:0正在生产,1可消费 boolean flag = false; //声明1把Lock锁 Lock lock = new ReentrantLock(); //生产者的等待队列 Condition producersCondition = lock.newCondition(); //消费者的等待队列 Condition consumersCondition = lock.newCondition(); public Product() { } //set和get方法 public Product(String band, String name) { this.band = band; this.name = name; } public String getBand() { return band; } public String getName() { return name; } public void setBand(String band) { this.band = band; } public void setName(String name) { this.name = name; } public void make(int i) { //如果消费线程正在消费,生产线程进入生产者的等待池等待 lock.lock(); try { if (flag == true) { try { //生产线程进入生产者的等待队列 producersCondition.await(); } catch (Exception e) { e.printStackTrace(); } } if (i % 2 == 0) { this.setBand("巧克力"); try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } this.setName("费列罗"); } else { this.setBand("啤酒"); try { Thread.sleep(100); } catch (Exception e) { e.printStackTrace(); } this.setName("哈尔滨"); } System.out.printf("生产者生产出%s,品牌为%s\r\n", this.getBand(), this.getName()); flag = true; // 唤醒消费队列中1个消费线程,来消费。 consumersCondition.signal(); } finally { lock.unlock(); } } public void cost() { lock.lock(); try { if (flag == false) { try { //消费线程进入消费者的等待队列 consumersCondition.await(); } catch (Exception e) { e.printStackTrace(); } } System.out.printf("消费者消费了%s,品牌为%s\n", this.getBand(), this.getName()); flag = false; //唤醒生产者队列中的1个生产线程,去生产 producersCondition.signal(); } finally { lock.unlock(); } } }
五、线程池
线程是属于操作系统地宝贵资源,频繁地创建和销毁线程, 会降低程序执行,浪费系统资源,所以需要线程池;
线程池是负责管理多个固定线程的容器,我们可以从线程池中获取线程,程执行完任务可以返回到线程中,继续等待使用。
线程池可以完成对线程资源的管理和复用;
1.有界阻塞队
有界阻塞队列:指定了指定具体长度/容量的队列,例如 ArrayBlockingQueue
有界阻塞队列特点:
- 提供了take和put这两个阻塞方法
- 当有界阻塞队列塞满时,还继续往队列中put数据,队列会一直阻塞,直到数据成功take走为止:
- 当有界阻塞队列中没有数据可以take是,还继续从队列中take数据,队列会一直阻塞,直到数据成功向队列中put数据之后:
应用场景
有界阻塞队列可在线程池中保存未及时处理的线程任务:
当线程池中核心线程和临时线程都处于工作状态时,多余的线程任务会被保存到有界阻塞队列中,当线程空闲了可以执行任务了,会自动从有界阻塞队列中take线程任务执行
2.无界阻塞队列
无界阻塞队列:没有知道具体长度/容量的队列,例如: LinkedBlockingQueue
无界阻塞队列特点:
- 不需要在创建的时候指定队列的长度/容量,
- 只要想存储可以一直存储
- 也提供了take和put阻塞方法
应用场景
无界阻塞队列可在线程池中保存未及时处理的线程任务:
使用无界阻塞队列,有可能出现任务的堆积,造成内存溢出
package com.itheima.pool; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingQueue; public class PoolDemo3 { public static void main(String[] args) throws InterruptedException { /* * * *有界阻塞队列:指定了指定具体长度/容量的队列,例如 ArrayBlockingQueue * 阻塞队列:BlockingQueue:具有阻塞功能的队列 * 特点:提供了take和put这两个阻塞方法 *当有界阻塞队列塞满时,还继续往队列中put数据,队列会一直阻塞,直到数据成功take走为止: *当有界阻塞队列中没有数据可以take是,还继续从队列中take数据,队列会一直阻塞,直到数据成功向队列中put数据之后: *有界阻塞队列可在线程池中保存未及时处理的线程任务: *当线程池中核心线程和临时线程都处于工作状态时,多余的线程任务会被保存到有界阻塞队列中,当线程空闲了可以执行任务了,会自动从有界阻塞队列中take线程任务执行; * * * 无界阻塞队列:没有知道具体长度/容量的队列,例如: LinkedBlockingQueue * 特点:不需要在创建的时候指定队列的长度/容量,只要想存储可以一直存储,也提供了take和put阻塞方法 * * */ LinkedBlockingQueue<Integer> strBlockingQueue=new LinkedBlockingQueue<>(); ArrayBlockingQueue<Integer> numberBlockingQueue=new ArrayBlockingQueue<>(1); numberBlockingQueue.put(10); System.out.println("添加了一个数据之后阻塞队列的内容是:"+numberBlockingQueue); Integer taked = numberBlockingQueue.take(); System.out.println("从阻塞队列中获取了一个数据:"+taked); numberBlockingQueue.put(20); } }
3.使用Executors创建线程池
使用Executors工具类可以快速创建1个线程池,但是可配置性低,适用性低 ;
package com.itheima.pool; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class PoolDemo1 { public static void main(String[] args) { //Executors.newFixedThreadPool():获取一个具备指定数量线程的线程池 ExecutorService executorService =Executors.newFixedThreadPool(10); //提交线程任务的方式 for (int i = 0; i < 15; i++) { executorService.submit(new Runnable() { @Override public void run() { System.out.println("线程名称为"+Thread.currentThread().getName()+"的线程执行了线程任务run()方法"); } }); } //当线程池创建完成之后,默认处于阻塞状态,一直等待提交线程任务 //关闭线程池:处理完所有线程任务之后 // executorService.shutdown(); //立即关闭线程池(没有处理完的线程任务会封装到一个List单列集合中返回) List<Runnable> runnables = executorService.shutdownNow(); System.out.println("没有执行完的线程任务有"+ runnables.size()); } }
4.自定义线程池配置参数说明
通过ThreadPoolExecutor的构造方法可以自定义1个线程池,以下是ThreadPoolExecutor() 构造方法的7个配置参数说明;
corePoolSize |
核心数数量,建议:IO密集型任务:当前机器CPU核心数 * 2 | CPU密集型任务:当前机器核心数+1 |
maximumPoolSize |
最大线程数=核心线程数+临时线程数,用于指定临时线程数量。 |
keepAliveTime |
临时线程最大存活时间 |
TimeUnit unit |
临时线程最大存活时间的时间单位,例如TimeUnit.SECONDS |
BlockingQueue<Runnable> workQueue |
用于存储核心线程+临时线程,暂时无法处理的线程任务; |
ThreadFactory |
线程工厂,线程池中的线程从哪里来,可以使用指定的线程工厂来创建。一般固定写法Executors.defaultThreadFactory() |
RejectedExecutionHandler |
默认拒绝处理策略:当线程任务数量 >(大于) 最大线程数量+阻塞队列长度,就会触发默认拒绝策略。 |
5.自定义线程池
创建1个线程池如下:
- 核心线程数为10
- 临时线程数为10
- 临时线程10秒内部工作就销毁
- 有界阻塞队列为5
- 默认拒绝策略为AbortPolicy;
使当前线程池最大能支持同时处理25个线程;
package com.itheima.pool; //自定义线程池,避免资源耗尽 import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class PoolDemo2 { public static void main(String[] args) { /* *ThreadPoolExecutor构造方法参数说明 *corePoolSize:核心数数量,建议:IO密集型任务:当前机器CPU核心数 * 2 | CPU密集型任务:当前机器核心数+1 *maximumPoolSize:最大线程数=核心线程数+临时线程数,用于指定临时线程数量。 *keepAliveTime:临时线程最大存活时间 *TimeUnit unit:临时线程最大存活时间的时间单位,例如TimeUnit.SECONDS *BlockingQueue<Runnable> workQueue:用于存储核心线程+临时线程,暂时无法处理的线程任务; * 如果想设置线程池处理任务的阈值:使用有界阻塞队列,线程池可处理任务=核心线程数+临时线程数+阻塞队列的长度, * 如果不想设置阈值:使用无界阻塞队列,有可能出现任务的堆积,造成内存溢出。 *ThreadFactory:线程工厂,线程池中的线程从哪里来,可以使用指定的线程工厂来创建。一般固定写法Executors.defaultThreadFactory() *RejectedExecutionHandler:默认拒绝处理策略:当线程任务数量 >(大于) 最大线程数量+阻塞队列长度,就会触发默认拒绝策略。 * ThreadPoolExecutor.AbortPolicy:一旦线程池无法处理提交的线程任务立即抛出异常(运行期异常) *DiscardPolicy: 一旦线程池无法处理提交的线程任务不会抛出异常,丢弃多余任务(丢弃多余任务) *DiscardOldestPolicy:一旦线程池无法处理提交的线程任务,用无法处理的任务替换阻塞队伍中等待时间最长的任务 *CallerRunsPolicy():一旦线程池无法处理提交的线程任务,让当前线程(main线程)处理。 * */ ThreadPoolExecutor pool = new ThreadPoolExecutor(10, 20, 10, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); //以上创建1个线程池:核心线程数为10 临时线程数为10 ,临时线程10秒内部工作就销毁,有界阻塞队列为5,默认拒绝策略为AbortPolicy //当前线程池最大能支持同时处理25个线程 for (int i = 0; i <= 25; i++) { pool.submit(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程名称为" + Thread.currentThread().getName() + "的线程执行了线程任务run()方法"); } }); } } }
参考
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 智能桌面机器人:用.NET IoT库控制舵机并多方法播放表情
· Linux glibc自带哈希表的用例及性能测试
· 深入理解 Mybatis 分库分表执行原理
· 如何打造一个高并发系统?
· .NET Core GC压缩(compact_phase)底层原理浅谈
· 手把手教你在本地部署DeepSeek R1,搭建web-ui ,建议收藏!
· 新年开篇:在本地部署DeepSeek大模型实现联网增强的AI应用
· Janus Pro:DeepSeek 开源革新,多模态 AI 的未来
· 互联网不景气了那就玩玩嵌入式吧,用纯.NET开发并制作一个智能桌面机器人(三):用.NET IoT库
· 【非技术】说说2024年我都干了些啥