多线程的使用
继承Thread ,实现Runable,实现Callable 传入FutureTask中
package com.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
class MyThread extends Thread{
@Override
public void run() {
System.out.println("继承Thread实现多线程"+Thread.currentThread().getName());
}
}
class MyRunable implements Runnable{
@Override
public void run() {
System.out.println("实现Runnable实现多线程"+Thread.currentThread().getName());
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
TimeUnit.SECONDS.sleep(3);
System.out.println("实现MyCallable实现多线程"+Thread.currentThread().getName());
return "callAble";
}
}
/**
* @author liuxu
* @date 2021/11/14 19:53
*/
public class ThreadDemo {
public static void main(String[] args) {
MyThread myThread = new MyThread();
myThread.setName("AA");
myThread.start();
Thread thread = new Thread(new MyRunable());
thread.setName("BB");
thread.start();
//FutureTask实现了Runable 所以和Runable一样使用只不过可以拿到返回值,阻塞返回
FutureTask<String> futureTask = new FutureTask(new MyCallable());
FutureTask<String> futureTask1 = new FutureTask(new MyCallable());
Thread thread1 = new Thread(futureTask);
Thread thread2 = new Thread(futureTask1);
thread1.start();
thread2.start();
try {
String s = futureTask.get();
String s1 = futureTask1.get();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
线程池的工作主要是控制线程的数量,处理过程中将任务放入队列,然后在线程池创建后启动这些任务,如果线程池数量超过了最大 数量,超出数量的线程排队等候,等待其他线程执行完毕,再从队列中取出任务来执行。
主要特点是: 线程复用,控制最大并发数,管理线程
优点:
降低资源消耗 通过复用降低了线程创建和销毁造成的消耗
提高响应速度 任务到达时,如果有空闲线程不需要创建线程就可以立即执行
提高线程的可管理性 可以对线程资源统一分配,优化和监控。
线程池的相关接口


常用线程池的使用方法
一共五个
- //创建单核心的线程池
- ExecutorService executorService = Executors.newSingleThreadExecutor();
- //创建固定核心数的线程池,这里核心数 = 2
- ExecutorService executorService = Executors.newFixedThreadPool(2);
- //创建一个按照计划规定执行的线程池,这里核心数 = 2
- ExecutorService executorService = Executors.newScheduledThreadPool(2);
- //创建一个自动增长的线程池
- ExecutorService executorService = Executors.newCachedThreadPool();
- //创建一个具有抢占式操作的线程池
- ExecutorService executorService = Executors.newWorkStealingPool();
下面是常用案例
package com.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author liuxu
* @date 2021/11/14 21:29
*/
public class ExcutorDemo {
public static void main(String[] args) {
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);//固定大小线程池 适用长期任务
ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();//单个线程 适用一个一个任务执行的场景
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); //大小自适应 适用多短期异步小程序或者负载比较轻的服务器
try{
for (int i = 0; i < 10; i++) {
/*fixedThreadPool.execute(()->{
//实现Ranable的内部类
System.out.println(Thread.currentThread().getName()+"\t 固定线程数线程办理业务");
});*/
/*singleThreadExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+"\t 单线程办理业务");
});*/
cachedThreadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"\t 自适应大小单线程办理业务");
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//fixedThreadPool.shutdown();
// singleThreadExecutor.shutdown();
cachedThreadPool.shutdown();
}
}
}
底层原理
ExecutorService构造 可以看出5大参数
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
//点进去可以看到7个参数
public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数
int maximumPoolSize,//能够容纳同时执行的线程数,此值必须大于1
long keepAliveTime,//多余的空闲线程的存活时间
TimeUnit unit,// 存活时间的时间单位
//当线程数超过corePoolSize 有空闲线程且空闲线程存活时间大于keepAliveTime,
//空闲线程就会销毁 直到线程数等于 corePoolSize
BlockingQueue<Runnable> workQueue, //任务队列,存放已提交但是未被执行的任务
ThreadFactory threadFactory,//创建线程的工厂,用默认的的就行
RejectedExecutionHandler handler) //拒绝策略,
队列满切工作线程大于maximumPoolSize时提交的任务的处理方式
调整线程池的7个参数可以设置不同的线程工作状态
状态1
添加的任务小于核心线程数 线程池中线程功能数
|
状态 |
添加任务个数 |
线程池中运行的线程个数 |
阻塞队列中任务 |
说明 |
|
状态1 |
<corePoolSize |
corePoolSize |
无 |
此时来任务会马上调用创建好的核心线程执行任务 |
|
状态2 |
>=corePoolSize |
>corePoolSize |
有 |
此时新加任务会被放入队列,如果有线程执行任务完毕,将从队列中拉取任务执行,此时无空闲线程, |
|
状态3 |
队列已满且运行线程数量<maximumPoolSize |
corePoolSize + |
有且已经最大 |
线程池会扩容但是小于maximumPoolSize,立即执行新添加的任务 |
|
状态4 |
队列已满且运行线程数量=maximumPoolSize |
maximumPoolSize |
有且已经最大 |
拒绝策略 |
|
当线程完成任务时,他会从队列中获取一个任务 | ||||
package com.thread;
import java.util.concurrent.*;
/**
* @author liuxu
* @date 2021/11/15 21:22
*/
public class ThreadPoolDemo {
public static void main(String[] args) {
/**
*如输出展示
* 8个任务进来
* 1 2被核心线程接受处理
* 3 4 5 等待区等待
* 6 7 8 任务进来时队列满座
* 新开3个线程达到最大线程 处理 6 7 8
* 最后 先完成任务的线程 再从队列中拉取 3 4 5执行
*/
ExecutorService service = new ThreadPoolExecutor(2, 5,
100L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
try {
for (int i = 1; i <= 8; i++) {
int tem = i;
service.execute(() -> {
System.out.println(Thread.currentThread().getName() + "线程处理任务" + tem);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
} finally {
service.shutdown();
}
}
}
线程池工作原理总结
1.创建线程池,大小为核心线程池大小,等待任务 Runable接口来临
2.任务来临,调用execute方法,做如下判断
2.1 如果当前运行的线程数小于核心线程数,立即使用创建好的核心线程执行任务
2.2 如果任务大于等于核心线程数,新来的任务放入队列
2.3 如果此时队列已满,且线程数小于最大线程数,立即创建线程执行任务
2.4 如果队列已满且线程数小于等于最大线程数,执行拒绝策略
3.当一个线程空闲达到一定时间,会做出判断
如果线程大于核心线程数,这个线程会被停止
所有任务完成后,线程池会缩减为核心线程池大小
线程池的拒绝策略
JDK内置的拒绝策略有四种 都继承了RejectedExecutionHandler
new ThreadPoolExecutor.AbortPolicy() //直接 抛出异常
new ThreadPoolExecutor.CallerRunsPolicy()//一种调节机制 ,建将任务返回给调用线程
new ThreadPoolExecutor.DiscardOldestPolicy());// 丢弃队列中等待最久的任务
new ThreadPoolExecutor.DiscardPolicy());//新来任务直接丢弃
可以在上面的代码中将最后一个参数替换后尝试效果
工作中实际上使用哪一个线程池
new ThreadPoolExecutor()明确的指定参数
如何合理配置线程池大小
CPU密集型 :CPU核数+1 while (true)
IO秘籍型 读取数据频繁 ,读取文件频繁: CPU核数*2 或者 CPU核数/(1-阻塞系数)
阻塞系数在0.8-0.9之间 即 10-12.5倍的 CPU核数
死锁
现象:线程A 持有锁A实体获取锁B 线程B持有锁B尝试获取锁A
原因:系统资源不足,进程推进顺序 不合适,资源分配不当
死锁代码展示
package com.thread;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class HoldThread implements Runnable{
private String lockA;
private String lockB;
public HoldThread(String lockA, String lockB) {
this.lockA = lockA;
this.lockB = lockB;
}
@Override
public void run() {
synchronized (lockA){
System.out.println(Thread.currentThread().getName()+"持有"+lockA+"尝试获取"+lockB);
try {
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "持有" + lockB + "尝试获取" + lockA);
}
}catch (Exception e){
e.printStackTrace();
}
}
}
}
/**
* @author liuxu
* @date 2021/11/16 20:42
*/
public class deadLockDemo {
public static void main(String[] args) {
new Thread(new HoldThread("A","B"),"A").start();
Thread thread = new Thread(new HoldThread("B", "A")
, "B");
thread.start(); //死锁
/*try { //非死锁
TimeUnit.SECONDS.sleep(3);
thread.start();
} catch (InterruptedException e) {
e.printStackTrace();
}*/
}
}
查找死锁位置
jps -l 找到死锁线程
jstack 22312(死锁线程号)找到死锁代码
Java多线程与线程池实战解析
本文详细介绍了Java中实现多线程的三种方式:继承Thread类、实现Runnable接口和使用Callable及FutureTask。并展示了线程池的工作原理、优缺点以及常用的线程池创建方法。通过实例代码解释了线程池的五种类型和线程池参数的配置。同时,文章提及了线程池的拒绝策略和实际工作中的线程池选择建议。最后,探讨了死锁的概念及其示例代码,以及如何查找死锁位置。
988

被折叠的 条评论
为什么被折叠?



