基础篇:高并发一瞥,线程和线程池的总结
- 进程是执行程序的实体,系统的调度执行单元,拥有独属的进程空间(内存、磁盘等)。而线程是进程的一个执行流程,一个进程可包含多个线程,共享该进程的所有资源:代码段,数据段(全局变量和静态变量),堆存储;但每个线程拥有自己的执行栈和局部变量
- 进程创建要分配资源,进程切换既要保存当前进程环境,也要设置新进程环境,开销大;而线程共享进程的资源,共享部分不需重分配、切换,线程的创建切换是小于进程的。因此更偏向使用线程提升程序的并发性
- 线程又分内核态和用户态,内核态可被系统感知调度执行;用户态是用户程序级别的,系统不知线程的存在,线程调度由程序负责
github地址,感谢star
1 JAVA线程的实现原理
- java的线程是基于操作系统原生的线程模型(非用户态),通过系统调用,将线程交给系统调度执行
- java线程拥有属于自己的虚拟机栈,当JVM将栈、程序计数器、工作内存等准备好后,会分配一个系统原生线程来执行。Java线程结束,原生线程随之被回收
- 原生线程初始化完毕,会调Java线程的run方法。当JAVA线程结束时,则释放原生线程和Java线程的所有资源
- java方法的执行对应虚拟机栈的一个栈帧,用于存储局部变量、操作数栈、动态链接、方法出口等
2 JAVA线程的生命周期
- New(新建状态):用new关键字创建线程之后,该线程处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量
- Runnable(就绪状态):当调用Thread.start方法后,该线程处于就绪状态。JVM会为其分配虚拟机栈等,然后等待系统调度
- Running(运行状态):处于就绪状态的线程获得CPU,执行run方法时,则线程处于运行状态
- Blocked(阻塞状态):阻塞状态是指线程放弃了cpu的使用权(join,sleep函数的调用),处于暂停止状态。Blocked状态的线程需要恢复到Runnable状态,才能再次被系统调度执行变成Running
- Terminated(线程死亡):线程正常run结束、或抛出一个未捕获的Throwable、调用Thread.stop来结束该线程,都会导致线程的死亡
- java线程和linux线程的生命周期基本是一一对应了,就是多了new阶段
3 JAVA线程的几种常用方法
- 线程启动函数
//Thread.java
//调用start启动线程,进入Runnable状态,等待系统调度执行
public synchronized void start(){//synchronized同步执行if (threadStatus != 0) //0 代表new状态,非0则抛出错误throw new IllegalThreadStateException();...start0(); //本地方法方法 private native void start0()...
}
//Running状态,新线程执行的代码方法,可被子类重写
public void run() {if (target != null) {//target是Runnable,new Thread(Runnable)时传入target.run(); }
}
- 线程终止函数
//Thread.java
@Deprecated public final void stop();
//中断线程
public void interrupt()
//判断的是当前线程是否处于中断状态
public static boolean interrupted()
- 用stop会强行终止线程,导致线程所持有的全部锁突然释放(不可控制),而被锁突同步的逻辑遭到破坏。不建议使用
- interrupt函数中断线程,但它不一定会让线程退出的。它比stop函数优雅,可控制
- 当线程处于调用sleep、wait的阻塞状态时,会抛出InterruptedException,代码内部捕获,然后结束线程
- 线程处于非阻塞状态,则需要程序自己调用interrupted()判断,再决定是否退出
- 其他常用方法
//Thread.java
//阻塞等待其他线程
public final synchronized void join(final long millis)
//暂时让出CPU执行
public static native void yield();
//休眠一段时间
public static native void sleep(long millis) throws InterruptedException;
- start与run方法的区别
- start是Thread类的方法,从线程的生命周期来看,start的执行并不意味着新线程的执行,而是让JVM分配虚拟机栈,进入Runnable状态,start的执行还是在旧线程上
- run则是新线程被系统调度,获取CPU时执行的方法,函数run则是继承Thread重写的run或者实现接口Runnable的run
- Thread.sleep与Object.wait区别
- Thread.sleep需要指定休眠时间,时间一到可继续运行;和锁机制无关,没有加锁也不用释放锁
- Object.wait需要在synchronized中调用,否则报IllegalMonitorStateException错误。wait方法会释放锁,需要调用相同锁对象Object.notify来唤醒线程
4 线程池及其优点
- 线程的每次使用创建,结束销毁是非常巨大的开销。若用缓存的策略(线程池),暂存曾经创建的线程,复用这些线程,可以减少程序的消耗,提高线程的利用率
- 降低资源消耗:重复利用线程可降低线程创建和销毁造成的消耗
- 提高响应速度:当任务到达时,不需要等待线程创建就能立即执行
- 提高线程的可管理性:使用线程池可以进行统一的分配,监控和调优
5 JDK封装的线程池
//ThreadPoolExecutor.java
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
- 1 corePoolSize:核心线程数,线程池维持的线程数量
- 2 maximumPoolSize:最大的线程数,当阻塞队列不可再接受任务时且maximumPoolSize大于corePoolSize则会创建非核心线程来执行。但任务执行时,会被销毁
- 3 keepAliveTime:非核心线程在闲暇间的存活时间
- 4 TimeUnit:和keepAliveTime配合使用,表示keepAliveTime参数的时间单位
- 5 workQueue:任务的等待阻塞队列,正在执行的任务数超过corePoolSize时,加入该队列
- 6 threadFactory:线程的创建工厂
- 7 handler:拒绝策略,线程数达到了maximumPoolSize,还有任务提交则使用拒绝策略处理
6 线程池原理之执行流程
//ThreadPoolExecutor.java
public void execute(Runnable command) {...if (workerCountOf(c) < corePoolSize) { //plan Aif (addWorker(command, true)) return;c = ctl.get();}if (isRunning(c) && workQueue.offer(command)) { //plan Bint recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}//addWorker(command, false) false代表可创建非核心线程来执行任务else if (!addWorker(command, false)) //plan Creject(command); // //plan D
}
- plan A:任务的execute,先判断核心线程数量达到上限;否,则创建核心线程来执行任务;是,则执行plan B
- plan B:当任务数大于核心数时,任务被加入阻塞队列,如果超过阻塞队列的容量上限,执行C
- plan C: 阻塞队列不能接受任务时,且设置的maximumPoolSize大于corePoolSize,创建新的非核心线程执行任务
- plan D:当plan A、B、C都无能为力时,使用拒绝策略处理
7 阻塞队列的简单了解
- 队列的阻塞插入:当队列满时,队列会阻塞插入元素的线程,直到队列不满
- 队列的阻塞移除:当队列为空时,获取元素的线程会等待队列变为非空
- BlockingQueue提供的方法如下,其中put和take是阻塞操作
操作方法 | 抛出异常 | 返回特殊值 | 阻塞线程 | 超时退出 |
---|---|---|---|---|
插入元素 | add(e) | offer(e) | put(e) | offer(e, timeout, unit) |
移除元素 | remove() | poll() | take() | pull(timeout, unit) |
检查 | element() | peek() | 无 | 无 |
- ArrayBlockingQueue
- ArrayBlockingQueue是用数组实现的有界阻塞队列,必须指定队列大小,先进先出(FIFO)原则排队
- LinkedBlockingQueue
- 是用链表实现的有界阻塞队列,如果构造LinkedBlockingQueue时没有指定大小,则默认是Integer.MAX_VALUE,无限大
- 该队列生产端和消费端使用独立的锁来控制数据操作,以此来提高队列的并发性
- PriorityBlockingQueue
public PriorityBlockingQueue(int initialCapacity, Comparator<? super E> comparator)
- 基于数组,元素具有优先级的无界阻塞队列,优先级由Comparator决定
- PriorityBlockingQueue不会阻塞生产者,却会在没有可消费的任务时,阻塞消费者
- DelayQueue
- 支持延时获取元素的无界阻塞队列,基于PriorityQueue实现
- 元素必须实现Delayed接口,指定多久才能从队列中获取该元素。
- 可用于缓存系统的设计、定时任务调度等场景的使用
- SynchronousQueue
- SynchronousQueue是一种无缓冲的等待队列,添加一个元素必须等待被取走后才能继续添加元素
- LinkedTransferQueue
- 由链表组成的TransferQueue无界阻塞队列,相比其他队列多了tryTransfer和transfer函数
- transfer:当前有消费者正在等待元素,则直接传给消费者,否则存入队尾,并阻塞等待元素被消费才返回
- tryTransfer:试探传入的元素是否能直接传给消费者。如果没消费者等待消费元素,元素加入队尾,返回false
- LinkedBlockingDeque
- LinkedBlockingDeque是由链表构建的双向阻塞队列,多了一端可操作入队出队,少了一半的竞争,提高并发性
8 Executors的四种线程池浅析
- newFixedThreadPool
//Executors.java
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
- 指定核心线程数,队列是LinkedBlockingQueue无界阻塞队列,永远不可能拒绝任务;适合用在稳定且固定的并发场景,建议线程设置为CPU核数
- newCachedThreadPool
//Executors.java
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
- 核心池大小为0,线程池最大线程数为最大整型,任务提交先加入到阻塞队列中,非核心线程60s没任务执行则销毁,阻塞队列为SynchronousQueue。newCachedThreadPool会不断的创建新线程来执行任务,不建议用
- newScheduledThreadPool
//Executors.java
public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory) {super(corePoolSize, Integer.MAX_VALUE,DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,new DelayedWorkQueue(), threadFactory);
}
//指定延迟执行时间
public <V> ScheduledFuture<V>
schedule(Callable<V> callable, long delay, TimeUnit unit)
- ScheduledThreadPoolExecutor(STPE)其实是ThreadPoolExecutor的子类,可指定核心线程数,队列是STPE的内部类DelayedWorkQueue。STPE的好处是 A 延时可执行任务,B 可执行带有返回值的任务
- newSingleThreadExecutor
//Executors.java
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())); //无界队列
}
- 和newFixedThreadPool构造方法一致,不过线程数被设置为1了。SingleThreadExecutor比new个线程的好处是;线程运行时抛出异常的时候会有新的线程加入线程池完成接下来的任务;阻塞队列可以保证任务按FIFO执行
9 如果优雅地关闭线程池
- 线程池的关闭,就要先关闭池中的线程,上文第三点有提,暴力强制性stop线程会导致同步数据的不一致,因此我们要调用interrupt关闭线程
- 而线程池提供了两个关闭方法,shutdownNow和shuwdown
- shutdownNow:线程池拒接收新任务,同时立马关闭线程池(进行中的任务会执行完),队列的任务不再执行,返回未执行任务List
public List<Runnable> shutdownNow() {...final ReentrantLock mainLock = this.mainLock;mainLock.lock(); //加锁try {checkShutdownAccess();advanceRunState(STOP);interruptWorkers(); //interrupt关闭线程tasks = drainQueue(); //未执行任务...
- shuwdown:线程池拒接收新任务,同时等待线程池里的任务执行完毕后关闭线程池,代码和shutdownNow类似就不贴了
10 线程池为什么使用的是阻塞队列
先考虑下为啥线程池的线程不会被释放,它是怎么管理线程的生命周期的呢
//ThreadPoolExecutor.Worker.class
final void runWorker(Worker w) {...//工作线程会进入一个循环获取任务执行的逻辑while (task != null || (task = getTask()) != null)...
}private Runnable getTask(){...Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); //线程会阻塞挂起等待任务,...
}
可以看出,无任务执行时,线程池其实是利用阻塞队列的take方法挂起,从而维持核心线程的存活
11 线程池的worker继承AQS的意义
//Worker class,一个worker一个线程
Worker(Runnable firstTask) {//禁止新线程未开始就被中断setState(-1); // inhibit interrupts until runWorkerthis.firstTask = firstTask;this.thread = getThreadFactory().newThread(this);
}final void runWorker(Worker w) {....//对应构造Worker是的setState(-1)w.unlock(); // allow interruptsboolean completedAbruptly = true;....w.lock(); //加锁同步....try {...task.run();afterExecute(task, null);} finally {....w.unlock(); //释放锁}
worker继承AQS的意义:A 禁止线程未开始就被中断;B 同步runWorker方法的处理逻辑
12 拒绝策略
- AbortPolicy 丢弃任务并抛出RejectedExecutionException异常
- DiscardOldestPolicy 丢弃队列最前面的任务,然后重新提交被拒绝的任务
- DiscardPolicy 丢弃任务,但是不抛出异常
- CallerRunsPolicy
A handler for rejected tasks that runs the rejected task directly in the calling thread of the {@code execute} method, unless the executor has been shut down, in which case the task is discarded.
如果任务被拒绝了,则由提交任务的线程执行此任务
13 ForkJoinPool了解一波
- ForkJoinPool和ThreadPoolExecutor不同,它适合执行可以分解子任务的任务,如树的遍历,归并排序等一些递归场景
- ForkJoinPool每个线程有一个对应的双端队列deque;当线程中的任务被fork分裂,分裂出来的子任务会放入线程自己的deque,减少线程的竞争
- work-stealing工作窃取算法
当线程执行完自己deque的任务,且其他线程deque还有多的任务,则会启动窃取策略,从其他线程deque队尾获取线程 - 使用RecursiveTask实现ForkJoin流程demo
public class ForkJoinPoolTest {public static void main(String[] args) throws ExecutionException, InterruptedException {ForkJoinPool forkJoinPool = new ForkJoinPool();for (int i = 0; i < 10; i++) {ForkJoinTask task = forkJoinPool.submit(new Fibonacci(i));System.out.println(task.get());}}static class Fibonacci extends RecursiveTask<Integer> {int n;public Fibonacci(int n) { this.n = n; }@Overrideprotected Integer compute() {if (n <= 1) { return n; }Fibonacci fib1 = new Fibonacci(n - 1);fib1.fork(); //相当于开启新线程执行Fibonacci fib2 = new Fibonacci(n - 2);fib2.fork(); //相当于开启新线程执行return fib1.join() + fib2.join(); //合并返回结果}}
}
欢迎指正文中错误
关注公众号,一起交流
参考文章
- Java线程和操作系统线程的关系
- 线程的3种实现方式
- 如何优雅的关闭Java线程池
- Java程序员必备的一些流程图
- JDK提供的四种线程池
- 7种阻塞队列相关整理
- 六种常见的线程池含ForkJoinPool
基础篇:高并发一瞥,线程和线程池的总结相关推荐
- c++ 线程池_基础篇:高并发一瞥,线程和线程池的总结
进程是执行程序的实体,拥有独属的进程空间(内存.磁盘等).而线程是进程的一个执行流程,一个进程可包含多个线程,共享该进程的所有资源:代码段,数据段(全局变量和静态变量),堆存储:但每个线程拥有自己的执 ...
- Python3高并发定时更新任务进程池和线程池的使用
Python3高并发定时更新任务进程池和线程池的使用 背景:需要定时对数据库的某一张表做更新 不管用多线程还是多进程只能利用当前一台机器的计算能力,可以考虑使用celery这样的工具,后期可以横向扩展 ...
- 并发编程基础篇——第二章(如何创建线程)
上节讲了基础概念,本章正式进入线程专题,对基础薄弱的同学可以好好看本章!! 1.Thread匿名子类 我们可以通过下面的代码来直接创建一个线程. // 构造方法的参数是给线程指定名字,推荐 Threa ...
- libevent c++高并发网络编程_高并发编程学习(2)——线程通信详解
前序文章 高并发编程学习(1)--并发基础 - https://www.wmyskxz.com/2019/11/26/gao-bing-fa-bian-cheng-xue-xi-1-bing-fa-j ...
- 高并发编程学习(2)——线程通信详解
前序文章 高并发编程学习(1)--并发基础 - https://www.wmyskxz.com/2019/11/26/gao-bing-fa-bian-cheng-xue-xi-1-bing-fa-j ...
- 多线程与高并发 笔记,非面向初学者 二:java引用,高并发多线程容器,线程池
网页右边,向下滑有目录索引,可以根据标题跳转到你想看的内容 如果右边没有就找找左边 上一节:JUC锁,一些面试题和源码讲解 1.引用 java引用共4种,强软弱虚 强引用:我们普通的new一个对象,就 ...
- java高并发(九)线程封闭
上一节介绍了不可变对象,通过在某些情况下将不会修改的类对象设计成不可变对象,来让对象在多个线程间保证对象是线程安全的.归根到底,相当于躲避开了并发问题,实现好的并发是一件很困难的事情,所以很多时候都想 ...
- 复制内存时检测到可能的io争用条件_这篇高并发服务模型大科普,内部分享时被老大表扬了...
面试中经常会被问到高性能服务模型选择对比,以及如何提高服务性能和处理能力,这其中涉及操作系统软件和计算机硬件知识,其实都是在考察候选人的基础知识掌握程度,但如果没准备的话容易一头雾水,这次带大家从头到 ...
- 终于肝了30篇高并发-学习贵在坚持
到今天为止终于将多线程给过了一遍,在发第一篇之前,已经花了比较多的时间做笔记,最近只是将笔记重新分析了一遍,然后拆分成合适的文章逐步完善和发布. 高并发与多线程是个非常广阔的大海,而且很多内容学习难度 ...
最新文章
- 机器人3D互动展品开发计划——飞拍视觉伺服
- 青龙面板修改端口教程避开5700更安全
- CSS3学习手记(10) 过渡
- spring 启动进度_在Web浏览器中显示Spring应用程序启动的进度
- 求图形学基本算法好书推荐?
- 解码(一):AVCodecContext解码上下文初始化讲解
- Java基础学习总结(179)——Java 日志框架冲突解决方案大汇总
- OpenCV算子速查表(持续更新)
- 机器学习精讲(向量逼近+函数逼近)学习笔记——第五章
- springboot学习笔记03 整合JPA
- Nginx/Apache 对图片,css,js等优化,静态页面设置过期时间
- 实现H5页面微信分享功能
- 微信推送封面尺寸_微信公众号文章封面图尺寸应该是多大?
- 计算机图形学原理与实践 答案,知到高级计算机图形学原理与实践单元测试答案...
- Kettle_使用资源库功能把ktr元数据统一存储到数据库中
- IMX6Q的DDR3初始化配置
- python输入一个整数和一个字符_【python零基础入门】基础语法之变量、字符串、数字、规则。...
- 载誉而归!昂视荣膺CAIMRS 2023「自动化创新奖」
- vscode使用ssh连接远程Ubuntu服务器(记录)
- C#多线程工业源码 替代传统plc搭载的触摸屏 工控屏幕一体机直接和plc通信
热门文章
- unity和ue4里面的玻璃材质笔记
- 运用cmd强制修改Mysql密码
- XPT2046的使用
- 全景视频拼接关键技术
- Numba加速Python教程
- 华为路由器 dopra linux passwd,【求助】华为悦me SA1456c Telnet shell 命令被极度阉割...
- 致广大用户关于 iPhone 电池及性能的说明
- Linux编译安装qt5.9,Linux CentOS7 安装 Qt 5.9.2
- 华为手机删视频后显示无服务器,华为手机总出现一些不明照片咋回事,原来你没关闭这个默认设置...
- hello,java