java并发编程
1. 前言
- 预备知识
- 线程安全问题,需要你接触过Java Web开发、Jdbc开发、Web服务器、分布式框架时才会遇到
- 基于JDK8,最好对函数式编程、lambda有一定了解
- 采用了slf4j打印日志,这是好的实践
- 采用了lombok简化java bean编写
- 给每个线程好名字,这也是一项好的实践
学习总纲:
图片
2. 进程与线程
- 进程和线程的概念
- 并行和并发的概念
- 线程基本应用
2.1 进程和线程
进程
-
概念:
- 进程是计算机中正在运行的程序的实例,它具有一定的独立功能,是系统进行资源分配和调度的一个独立单位
- 进程可以包含代码、数据和堆栈等资源,是程序在计算机中执行的基本单位
- 一个进程可以包含多个线程,线程之间共享进程的资源
- 进程就可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360安全卫士等)
-
特征:并发性、异步性、动态性、独立性、结构性
线程
- 概念:
- 线程是进程的一个实体,是CPU 调度和分派的基本单位
- 线程之间共享进程的内存空间和其他系统资源
- 线程可以看做是一个轻量级的进程,它允许一个程序在同一时间执行多个任务,从而提高程序的执行效率
进程线程的区别
- 资源占用:进程拥有独立的资源,而线程共享进程的资源。
- 执行效率:线程比进程更轻量级,线程之间的切换比进程之间的切换更高效
- 任务调度:线程是 CPU 调度的基本单位,一个进程内的所有线程共享相同的
2.2 串行,并行,并发
-
串行:在计算机系统中,多个任务一个接一个地顺序执行(单核处理器)
-
并行:在计算机系统中,多个任务在同一时刻同时执行(多核处理器)
-
并发:在计算机系统中,多个任务在同一时间段内执行,但不是同时执行。这意味着并发可以通过任务切换来实现,在单核处理器系统中,CPU 会在不同任务之间进行快速切换,以实现并发执行
案例:
- 串行:家庭主妇做饭、打扫卫生、给孩子喂奶,做完一件事再去做另一件事
- 并行:家庭主妇雇了3个保姆,一个专做饭、一个专打扫卫生、一个专喂奶,互不干扰
- 并发:家庭主妇做饭、打扫卫生、给孩子喂奶,她一个人轮流交替做这多件事
2.3 线程基本应用
- 同步:需要等待结果返回,才能继续运行就是同步
- 异步:不需要等待结果返回,就能继续运行就是异步
结论:
- 在项目中,视频文件需要转换格式等操作比较费时,这时开一个新线程处理视频转换,避免阻塞主线程
- tomcat 的异步servlet 也是类似的目的,让用户线程处理耗时较长的操作,避免阻塞tomcat 的工作线程
- ui程序中,开线程进行其他操作,避免阻塞ui线程
结论:
- 单核CPU下,多线程不能提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用CPU,不至于一个线程总占用CPU,别的线程没法运行
- 多核CPU可以并行跑多个线程,但能否提高程序运行效率还是要分情况。将任务拆分,并行执行,可以提高程>序的运行效率。
- 注意:不是所有计算任务都能拆分,也不是所有任务都需要拆分
- IO操作不占用CPU,只是我们一般拷贝文件使用的是【阻塞TO】,这时相当于线程虽然不用CPU,但需要一直等待IO结束,没能充分利用线程。所以才有后面的【非阻塞IO】和【异步IO】优化
3. 线程
3.1 线程的创建
3.1.1创建线程方法一
- 匿名内部类,创建线程类
//匿名内部类,创建线程对象 Thread t = new Thread(){ @Override public void run(){ //执行的任务 } }; //启动线程 t.start(); //创建线程类 //构造方法的参数是给线程指定名字-->推荐 Thread t1 = new Thread("t1") { @override public void run( ) { log.debug("hello"); } }; t1.start(); t1.setName("t1");
创建线程方法二:
- 使用Runnable配合Thread把线程和任务(要执行的代码)分开
- Thread:线程
- Runnable:可运行的任务(线程要执行的代码)
Runnable runnable = new Runnable(){ @Override public void run(){ //要执行的任务 } } Thread t1 = new Tread(runnable,"t1"); t1.start();
//java 8 之后是使用lambda 精简代码 //优化的是创建任务对象 Runable task = ()->{log.debug("hello");} //参数1 是任务对象;参数2 是线程名字 Thread t2 = new Tread(task2,"t2"); t2.start();
Thread与Runnable的关系
-
线程(Thread)是 Java 中用于实现多线程的类,它是一个具体的事物,可以被创建、启动、停止等。一个线程可以执行一个或多个任务
-
Runnable接口是一个抽象接口,它只有一个方法run()。任何实现了Runnable接口的类都可以作为一个线程的目标对象。Runnable接口是定义线程执行任务的逻辑,而线程则是具体执行这些任务的实体
-
线程(Thread)和Runnable接口之间的关系是:线程需要一个目标对象来执行任务,这个目标对象可以是一个实现了Runnable接口的类。
-
Thread是把线程和任务合并在了一起,Runnable是把线程和任务分开了,更容易与线程池等高级API配合
-
通过实现Runnable接口,我们可以将任务逻辑与线程分离,从而实现多线程编程
-
创建线程方法三
- FutureTask能够接收Callable类型的参数,用来处理有返回结果的情况
FutureTask<Integer> task = new FutureTask(){ @Override public Integer call() throws Exception{ log.debug("running..."); return 100; } }; Thread thread = new Thread(task,"t1"); thread.start(); task.get();
3.2 线程的运行
查看进程的方法
-
windows
- 任务管理器可以查看进程和线程数,也可以用来杀死进程
- tasklist查看进程
- taskkill杀死进程
-
LIinux
- ps -fe查看所有进程
- ps -fT -p 查看某个进程(PID)的所有线程
- kill杀死进程
- top按大写H切换是否显示线程
- top -H -p 查看某个进程(PID)的所有线程
-
Java
- jps查看所有Java进程
- jstack 查看某个Java进程(PID)的所有线程状态
栈与栈帧
- 栈内存是线程使用,每个线程启动后,虚拟机就会为其分配一块栈内存
- 每个栈由多个栈帧组成,对应着每次方法调用时所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法,一个栈帧对应一个方法
线程上下文切换(Thread Context Switch)
-
cpu不再执行当前的线程,转而执行另一个线程的代码
-
原因:
- 线程的cpu时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了sleep、yield、wait、join、park、synchronized、lock等方法
-
程序计数器的作用是记住下一条jvm指令的执行地址
- 当上下文切换发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态
-
状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
常见方法
start()与run()方法
-
run() 方法:run() 方法是线程类(如 Thread 类)中的一个方法,它定义了线程执行的任务。当线程启动时,run() 方法将被自动执行。run() 方法的主要作用是为线程赋予一个任务,告诉线程执行什么操作。
-
start() 方法:start() 方法是线程类(如 Thread 类)中的一个方法,它用于启动新线程。当调用 start() 方法时,线程将开始执行 run() 方法中的代码。start() 方法的主要作用是启动线程,让线程从 run() 方法开始执行。
-
run()方法和start()方法的区别:
- 方法性质不同:run 是一个普通方法,而 start 是开启新线程的方法。
- 执行速度不同:调用 run 方法会立即执行任务,调用 start 方法是将线程的状态改为就绪状态,不会立即执行
- 调用次数不同:run 方法可以被重复调用,而 start 方法只能被调用一次
yied()和sleep()
- sleep
- 调用sleep会让当前线程从Ruming进入Timed Waiting状态
- 其它线程可以使用interrupt方法打断正在睡眠的线程,这时sleep方法会抛出工interruptedException
- 睡眠结束后的线程未必会立刻得到执行
- 建议用TimeUnit的sleep代替Thread 的sleep来获得更好的可读性
- yield
- yield()方法是线程类(如Thread类)中的一个方法,它用于让当前线程放弃 CPU 执行权,将执行机会交给其他线程
- 调用yield会让当前线程从Running进入Runnable状态,等待 CPU 分配执行时间片,如果当前线程是优先级最高的线程,它将立即恢复执行
- 具体的实现依赖于操作系统的任务调度器
线程优先级
- 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
- 优先级1-10,数值越高,级别越高
- 如果cpu比较忙,那么优先级高的线程会获得更多的时间片,但cpu闲时,优先级几乎没作用
sleep的应用
-
放在cpu占百分百
-
在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield或 sleep来让出cpu的使用权给其他程序
while(true){ try{ Thread.sleep(50); }catch (InterruptedException e){ e.printstackTrace(); } }
-
可以用wait或条件变量达到类似的效果
-
wait和条件变量两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景(加锁同步)
-
sleep适用于无需锁同步的场景
join方法
static int r = 0;
public static void main(string[] args) throws InterruptedException {
test1();
}
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
},"t1");
t1.start();
log.debug("结果为:{}", r);
log.debug("结束");
}
//r = 0
分析:
- 因为主线程和线程t1是并行执行的, t1线程需要1秒之后才能算出r=10
- 而主线程一开始就要打印r的结果,所以只能打印出r=0
解决方法 - 用sleep行不行?为什么?
- 时间不好把握
- 使用 join,加在t1.start()之后即可
t1.join()
,等待t1线程运行结束
应用的同步
- 以调用方角度:
- 需要等待结果返回,才能继续运行就是同步
- 不需要等待结果返回,就能继续运行就是异步
- 多个线程同步的结果就是调用每个线程的join方法
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1);
r1 = 10;
});
Thread t2 = new Thread(() -> {
sleep(2);
r2 = 20;
);
long start = system.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
long end = system.currentTimeMillis();
log.debug("r1:{} r2: {i}cost: {", r1,r2, end - start);
//r1=10,r2=20
join(long n)限时同步
- 如果线程内部任务需要的时间>n,则线程不会执行完
- 如果线程内部任务需要的时间<=n,则线程提前结束
interrupt方法
-
interrupt方法打断sleep,wait,join的线程
private static void test1() throws InterruptedException { Thread t1 = new Thread(()->{ sleep(1); },"t1"); t1.start(); sleep(2); t1.interrupt();//打断线程 //isInterrupted方法获取打断标记 log.debug("打断状态:{}", t1.isInterrupted());//false //输出打断异常
- isInterrupted方法获取打断标记
- 线程正常状态被打断:返回true
- 线程在sleep,wait,join的状态被打断:返回false
- isInterrupted方法获取打断标记
-
interrupt
方法打断正常线程 -
正常运行的线程被调用
interrupt
方法是不会被停止的,所以可以使用isInterrupted
方法获得打断标记private static void test1() throws InterruptedException { Thread t1 = new Thread(()->{ while(true){ boolean interrupted = Thread.currentTread().isInterrupted(); if(interrupted){ break; } } },"t1"); t1.start(); sleep(2); t1.interrupt();//打断线程
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!