Java的基本知识之线程池篇
1、基本概念
1、共享资源
多个线程对同一份资源进行访问(读写操作),该资源被称为共享资源。如何保证多个线程访问到的数据是一致的,则被称为数据同步或资源同步。
2、线程通信
线程通信,又叫进程内通信,和网络通信等进程间通信不同;多个线程实现互斥资源访问的时候,会互相发送信号。
2、同步、异步、阻塞、非阻塞
同步和异步是 获取结果的方式,阻塞和非阻塞是 等待结果中是否能够完成其他事情。
同步阻塞(BIO),需要等待读取完客户端的数据,同时需要阻塞的判断客户端是否有数据。
同步非阻塞(NIO),需要等待读取完客户端的数据,但是轮询的方式判断客户端是否有数据,可以去做其他事情。
异步阻塞,等待结果回调通知,cpu处于等待的休眠中。
异步非阻塞(AIO),操作系统来完成读取的操作,读取完毕通知回调,使用线程池的方式去轮询客户端是否有数据,可以去做其他事情。
并发,单个 cpu 可以处理多个任务,但是同一时刻只有一个任务在运行。
并行,多个任务在多个 cpu 上运行,实现真正的同一时刻运行。
2、生命周期
3、守护线程
一般使用 new Thread 创建的线程都是非守护线程,也称用户线程。设置为守护进行的方式就是,在 run 之前,调用setDaemon(true)。守护线程,仅仅是为用户线程提供服务,那么一旦所有的用户线程都运行完毕,守护进行也会结束。相反,只要有非守护线程存在,那么守护线程就不会终止。
守护线程的应用场景:垃圾回收、心跳检测、拼音检查线程
package com.vim;
import java.util.concurrent.TimeUnit;
public class App {
public static void main( String[] args ) throws Exception{
Thread t = new Thread(()->{
while (true){
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("......");
}catch (Exception e){
e.printStackTrace();
}
}
});
//只有t设置了此处,才会随着父进程的退出而退出
t.setDaemon(true);
t.start();
TimeUnit.SECONDS.sleep(5);
System.out.println("main is over!");
}
}
4、线程 yield、sleep
yield,称为线程让步,从 RUNNING 状态转为 RUNNABLE 状态,有可能切换之后,再次抢到执行权,进入 RUNNING 状态。
sleep,会阻塞该线程,进入 BLOCKED 状态,此时是挂起,让出cpu;只有阻塞时间到了,才会进入 RUNNABLE 状态,并不一定马上获取到 cpu 的执行权。
sleep(0) 的作用是,触发操作系统立刻重新进行一次CPU竞争,竞争的结果也许是当前线程仍然获得CPU控制权,也许会换成别的线程获得CPU控制权。
package com.vim;
public class App {
public static void main( String[] args ) throws Exception {
new Thread(()->{
try {
while (true){
Thread.sleep(0);
}
}catch (Exception e){
e.printStackTrace();
}
}).start();
}
}
5、线程 interrupt
wait、sleep、join 使当前线程进入阻塞状态,而 interrupt 可以打断阻塞,不过仅仅是打算当前的阻塞状态。当前线程会抛出一个 InterruptException 异常,就像一个信号通知。此通知会将 interrupt flag 置为 true,不过针对阻塞状态下的中断,该状态位会被重置为 false。通过 thread.isInterrupted() 来判断,当然,阻塞状态下的,会被清除,从而影响该方法的结果。
package com.sample.modules.test;
import java.util.concurrent.TimeUnit;
public class Test {
//中断一个线程,中断位 true
public static void test1() throws Exception{
Thread t = new Thread(()->{
//2.获取当前的 interrupt flag 状态为 true
System.out.println(Thread.currentThread().isInterrupted());
});
t.start();
//1.中断线程
t.interrupt();
TimeUnit.MINUTES.sleep(4);
}
//中断一个线程,中断位 false
public static void test2() throws Exception{
Thread t = new Thread(()->{
//2.清除中断位
Thread.interrupted();
//3.获取当前的 interrupt flag 状态为 false
System.out.println(Thread.currentThread().isInterrupted());
});
t.start();
//1.中断线程
t.interrupt();
TimeUnit.MINUTES.sleep(4);
}
//中断一个线程,中断位 false
public static void test3() throws Exception{
Thread t = new Thread(()->{
//2.中断wait、sleep、join导致的阻塞
try {
TimeUnit.SECONDS.sleep(5);
}catch (InterruptedException e){
System.out.println("i am interrupted");
}
//3.获取当前的 interrupt flag 状态为 false
System.out.println(Thread.currentThread().isInterrupted());
});
t.start();
//1.中断线程
t.interrupt();
TimeUnit.MINUTES.sleep(4);
}
//阻塞之前中断结果:false、true、i am interrupted、false
public static void test4(){
Thread.interrupted();
System.out.println("interrupt flag: "+Thread.currentThread().isInterrupted());
Thread.currentThread().interrupt();
System.out.println("interrupt flag: "+Thread.currentThread().isInterrupted());
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
System.out.println("i am interrupted");
}
System.out.println("interrupt flag: "+Thread.currentThread().isInterrupted());
}
public static void main(String[] args) throws Exception{
test3();
}
}
6、线程 join
parent 线程调用 child 线程的 join 方法,实际上调用的是 join(0) ,该方法加了锁,循环判断 child 线程的存活状态,当然,每次循环中调用 wait(0) 方法,这样的话可以让其他线程也进入 join(0) 方法。
//当前方法没有上锁
public final void join() throws InterruptedException {
join(0);
} 无锡看男科医院哪家好 https://yyk.familydoctor.com.cn/20612/
//此方法上锁,此时其他线程可以进入 join(),但不可以进入 join(0)
//不断的检查线程是否 alive,调用 wait(0),这样就释放了锁,其他的线程就可以进入 join(0),也就是可以有多个线程等待某个线程执行完毕
//一旦线程不在 alive,那么就会返回到 join() 方法,调用的线程就可以继续执行下去
public final synchronized void join(long millis){
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
}
7、线程通知 notify、wait
这两个方法,来源于 Object 类,两者配合使用。wait 方法属于对象方法,在调用之前必须先获取该对象的 monitor 锁,调用之后,就会释放该对象的 monitor 锁,从而进入该对象关联的 waitset 中,等待其他线程使用 notify 唤醒。
典型的生产消费场景:
生产者,在生产产品时,对仓库(同步资源)进行上锁,如果当前仓库没有满,则放入产品,使用 notifyAll 通知消费者;如果当前仓库满了,则使用 wait 释放锁,进入 waitset 阻塞等待 notifyAll 通知。
消费者,来到仓库消费,对仓库(同步资源)进行上锁,如果当前仓库有产品,则拿走产品,使用 notifyAll 通知生产者;如果当前仓库是空的,则使用 wait 释放锁,进入 waitset 阻塞等待 notifyAll 通知。
当 notifyAll 来临的时候,针对所有的生产者和消费者来说,都有拿到仓库钥匙的机会,就会再去竞争,再次进入以上判断逻辑。
8、wait 和 sleep 的区别
相同之处:
使线程进入到阻塞状态;可以被 interrupt 中断;
不同之处:
wait 是 Object 共有,sleep 是 Thread 特有。
wait 必须运行在同步方法中,sleep不需要。
wait 会释放锁,如果sleep放在同步方法中,并不会释放锁。
9、线程异常处理
package com.sample.modules.test;
public class Test {
public static void main(String[] args) throws Exception{
Thread t = new Thread(()->{
int i = 1/0;
});
t.setUncaughtExceptionHandler((thread, e)->{
System.out.println("exception...");
});
t.start();
}
}
10、计算机内存模型 和 Java 内存模型
原理追溯:cpu 在执行指令的时候,数据来源于主内存(RAM),由于两者速度的严重不对等,之间出现了缓存 cache;一般分为 L1、L2、L3 缓存,每个 CPU 核心包含一套 L1,共享 L2 和 L3 缓存。
缓存一致性问题:当多个处理器的运算任务都涉及同一块主内存区域时,每个 cpu 从主内存中取出变量,放到本地 cache 中,进行计算之后,写入到 cache 中,再由 cache 刷新到主内存中。
读取主内存 i 到 cache 中
对 i 进行 ++
将结果写回 cache
将 cache 刷新回主内存。
缓存一致性协议:cpu 在操作 cache 中的数据时,如果发现是一个共享变量,那么在写入的时候,会发出信号通知其他的 cpu 将该变量的 cache line 置为无效状态,其他 cpu 在进行该变量读取的时候,就需要去主内存中读取。
相比计算机内存模型,Java 内存模型: 线程 == CPU, 工作内存 == CPU cache,主内存 == 主内存。
12、并发编程三大特性
原子性,多次操作中,要么全部得到执行,要么全部不执行。
可见性,一个线程对共享变量,作了修改,那么其他线程立即可以看到修改后的值。
有序性,程序代码在执行过程中的先后顺序。
13、synchronized 关键字
synchronized 关键字提供了一种锁的机制,能够保证共享变量的互斥访问,即同一时刻,只能有一个线程访问同步资源。
内存方面,monitor enter 和 monitor exit 两个 JVM 指令,保证了任何线程在 monitor enter 之前必须从主内存中获取数据,在 monitor exit 之后,必须把更新的值刷新到主内存中。这两个 JVM 指令,严格的遵守 happends-before 原则,即一个 monitor exit 指令之前必须有一个 monitor enter 指令存在。
该关键字对于同步资源的排他性访问,很有效,但是其他没有获取到 monitor 的线程,到底阻塞多久,能不能提前解除阻塞,这些都是未知的。为此,java 为我们提供了其他的解决方案,显式锁。如 ReentrantLock。
14、AQS
独占模式
#获取
1、尝试获取资源成功立即返回;失败的话,创建独占节点,利用CAS加入到队列尾部,进入自旋状态
2、如果前一个节点是头节点,再次尝试获取资源,成功设置为头节点;否则挂起,等待被前驱节点唤醒
#针对可中断来说
1、普通的获取,在发生了中断后,会清除中断位,并在获取资源成功后,触发一次中断
2、可中断的获取,在发生了中断后,也会清除中断位,但是直接抛出 interrupted 异常
#释放
1、释放同步状态
2、获取当前节点的下一个节点,唤醒
共享模式
#获取
1、获取同步状态,如果返回值>=0,则说明同步状态(state)有剩余,获取锁成功直接返回
2、失败,向队列尾部添加一个共享类型的Node节点,随即该节点进入自旋状态
3、前驱节点如果为头节点,再次判断同步状态是否(state)有剩余
4、如果是,则说明当前节点可执行,同时把当前节点设置为头节点,并且唤醒所有后继节点
两者区别
1、独占锁的同步状态值为1,即同一时刻只能有一个线程成功获取同步状态;共享锁的同步状态>1,取值由上层同步组件确定
2、独占锁队列中头节点运行完成后释放它的直接后继节点;共享锁队列中头节点运行完成后释放它后面的所有节点
3、共享锁中会出现多个线程(即同步队列中的节点)同时成功获取同步状态的情况
15、重入锁 ReentrantLock
#获取
1、如果当前有锁,且拥有者是当前线程,再次增加重入
2、如果当前无锁,直接尝试获取锁,公平模式会判断是否有等待的线程,非公平模式下直接尝试独占资源
16、计数器 CountDownLatch
1、使用的是共享模式,初始化时设置 state 一个固定的数量
2、await 方法,调用 sync 的中断 acquireShared 方法,重写 tryAcquireShared 获取方式,当 state 到达0的时候,才表示资源可获取
3、countDown 方法,调用 sync 的 releaseShared 方法,不断的对 state 进行减一
17、读写锁 ReentrantReadWriteLock
写锁
#获取
1、当前处于无锁状态,独占模式获取写锁
2、如果设置了 writerShouldBlock,直接返回 false
1、当前处于有锁状态
2、有读锁,直接返回 false
3、有写锁,如果是当前线程,则重入,否则返回 false
读锁
1、如果有写锁,并且写锁不是当前线程,返回 -1
2、如果没有写锁,尝试获取读锁,如果设置了 readerShouldBlock,进入再次判断
Java的基本知识之线程池篇的更多相关文章
- C#多线程之线程池篇3
在上一篇C#多线程之线程池篇2中,我们主要学习了线程池和并行度以及如何实现取消选项的相关知识.在这一篇中,我们主要学习如何使用等待句柄和超时.使用计时器和使用BackgroundWorker组件的相关 ...
- C#多线程之线程池篇2
在上一篇C#多线程之线程池篇1中,我们主要学习了如何在线程池中调用委托以及如何在线程池中执行异步操作,在这篇中,我们将学习线程池和并行度.实现取消选项的相关知识. 三.线程池和并行度 在这一小节中,我 ...
- Java 1.ExecutorService四种线程池的例子与说明
1.new Thread的弊端 执行一个异步任务你还只是如下new Thread吗? new Thread(new Runnable() { @Override public void run() { ...
- JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor
JUC源码分析-线程池篇(三)ScheduledThreadPoolExecutor ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor.它主要用来在 ...
- JUC源码分析-线程池篇(二)FutureTask
JUC源码分析-线程池篇(二)FutureTask JDK5 之后提供了 Callable 和 Future 接口,通过它们就可以在任务执行完毕之后得到任务的执行结果.本文从源代码角度分析下具体的实现 ...
- JUC源码分析-线程池篇(三)Timer
JUC源码分析-线程池篇(三)Timer Timer 是 java.util 包提供的一个定时任务调度器,在主线程之外起一个单独的线程执行指定的计划任务,可以指定执行一次或者反复执行多次. 1. Ti ...
- JUC源码分析-线程池篇(一):ThreadPoolExecutor
JUC源码分析-线程池篇(一):ThreadPoolExecutor Java 中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池.在开发过程中,合理地使用线程池 ...
- C#多线程之线程池篇1
在C#多线程之线程池篇中,我们将学习多线程访问共享资源的一些通用的技术,我们将学习到以下知识点: 在线程池中调用委托 在线程池中执行异步操作 线程池和并行度 实现取消选项 使用等待句柄和超时 使用计时 ...
- java多线程总结五:线程池的原理及实现
1.线程池简介: 多线程技术主要解决处理器单元内多个线程执行的问题,它可以显著减少处理器单元的闲置时间,增加处理器单元的吞吐能力. 假设一个服务器完成一项任务所需时间为:T1 创 ...
随机推荐
- C#利用iTextSharp将datatable数据转化为PDF文件
1.下载iTextSharp.dll文件 下载链接:https://pan.baidu.com/s/14o-pJ-U2yU8n0EyIn249qg 提取码:tklu 2.PDF转换方法 /// < ...
- [转]理解Vuex的辅助函数mapState, mapActions, mapMutations用法
原文地址:https://www.cnblogs.com/tugenhua0707/p/9794423.html 在讲解这些属性之前,假如我们项目的目录的结构如下: ### 目录结构如下: demo1 ...
- Java基础 awt Button 鼠标放在按钮上背景颜色改变,鼠标离开背景颜色恢复
JDK :OpenJDK-11 OS :CentOS 7.6.1810 IDE :Eclipse 2019‑03 typesetting :Markdown code ...
- android 8.1 wifi提示"已连接 但无法访问互联网"的解决办法
主要是GFW的问题 adb shell以下命令解决 settings put settings put settings put settings put global captive_portal_ ...
- angular自定义module
在app.module.ts里面,imports部分,添加你的自定义模块名在你的自定义模块内,添加了component以后,需要添加exports导出,类似下面 import { NgModule } ...
- flutter upgrade之后出现Attribute application@appComponentFactory value=(android.support.v4.app.CoreComponentFactory) from
错误信息 Initializing gradle... Resolving dependencies... Running Gradle task 'assembleDebug'... /Users/ ...
- /bin/sh^M:解释器错误:没有那个文件或目录
在win下编辑的时候,换行结尾是\n\r , 而在linux下 是\n,所以才会有 多出来的\rsed -i 's/\r$//' configure 删除configure脚本中的\r
- springboot放到linux启动报错:The temporary upload location [/tmp/tomcat.8524616412347407692.8111/work/Tomcat/localhost/ROOT/asset] is not valid
1.背景 笔者的springboot在一个非root用户环境下运行,这种环境下可以保证不被潜在的jar/开源框架漏洞提权. 比如在防火墙上把外网访问来的443端口映射到本地8443的java web端 ...
- Python - Django - 中间件 process_view
process_view 的执行顺序也是按照 settings.py 中的顺序来执行 process_view 在 urls.py 的对应关系之后,在执行视图函数之前执行 如果返回 None,则继续执 ...
- qtableview 鼠标划过单元格弹出标签显示单元格内容
QStandardItem *item = new QStandardItem(show_content); infoTableModel->setItem(1, 1, item); item- ...