⿊马程序员JUC并发教程笔记(⼀)
课程来源:⿊马程序员《JUC并发教程⼀》
视频地址:
1. 进程与线程
1.1 进程与线程
进程
程序由指令和数据组成,是⼀个静态的概念。当程序被加载到内存中运⾏起来时为进程,进程是动态的概念
进程是⽤来加载指令、管理内存、管理IO的
当⼀个程序被运⾏,从磁盘加载这个程序的代码到内存中,这就相当于开启了⼀个线程
进程可以视为程序的⼀个实例
线程
⼀个进程可以有⼀到多个线程,最少有⼀个线程
进程是资源分配的单位;线程是cpu调度的单位。在Windows中进程是不活动的,只作为线程的容器
进程 VS 线程
进程之间是相互独⽴的,每个进程都有⾃⼰的运⾏空间,不允许相互窥探;线程存在于进程内,共享进⾏的内存
进程拥有⾮配得资源,如内存空间等,供线程共享
进程通信复杂
同⼀台计算机的进程之间通信称为IPC(Inter-process communication)
不同计算机(如客户机与服务器)之间的进程通信,需要通过⽹络,遵守共同的协议,如Http、Https 线程通信相对简单,因为他们共享进程的内存空间,可以通过共享变量的⽅式进⾏通信
线程更轻量,线程上下⽂切换的成本⽐进程低
1.2 并⾏与并发
并发
并发(Concurrent),在操作系统中,是指⼀个时间段中有⼏个程序都处于已启动运⾏到运⾏完毕之间,且这⼏个程序都是在同⼀个处理机上运⾏。
并发不是真正意义上的“同时进⾏”,只是CPU把⼀个时间段划分成⼏个时间⽚段(时间区间),然后在这⼏个时间区间之间来回切换,由于CPU处理的速度⾮常快,只要时间间隔处理得当,即可让⽤户感觉是多个应⽤程序同时在进⾏。如:打游戏和听⾳乐两件事情在同⼀个时间段内都是在同⼀台电脑上完成了从开始到结束的动作。那么,就可以说听⾳乐和打游戏是并发的。
并⾏
并⾏(Parallel),当系统有⼀个以上CPU时,当⼀个CPU执⾏⼀个进程时,另⼀个CPU可以执⾏另⼀个进程,两个进程互不抢占CPU资源,可以同时进⾏,这种⽅式我们称之为并⾏(Parallel)。
其实决定并⾏的因素不是CPU的数量,⽽是CPU的核⼼数量,⽐如⼀个CPU多个核也可以并⾏。
并发 VS 并⾏
并发是宏观的概念,并⾏是微观的概念
只有在多喝处理器上才会存在并⾏
并发的多个任务之间相互抢占资源
并⾏的多个任务之间不会恍惚抢夺资源
1.3 应⽤
1. 异步调⽤
同步:按照代码顺序执⾏,需要等待结果返回才能继续运⾏
异步:按照代码顺序执⾏,不需要等待结果返回就能继续运⾏
场景
⽐如在项⽬中,视频⽂件需要转换格式等操作⽐较费时,这时开⼀个新线程处理视频转换,避免阻塞主线
tomcat 的异步 servlet 也是类似的⽬的,让⽤户线程处理耗时较长的操作,避免阻塞 tomcat 的⼯作线程ui 程序中,开线程进⾏其他操作,避免阻塞 ui 线程
2. 提⾼效率
A=a1+a2+a3
如果⼀个⼤的任务可以拆分为⼏个独⽴的⼩的任务,如,那么⼤任务A就可以拆分为三个⼩任务。把这三个任务放在三个线程上执⾏,总的任务时间取决于耗时最长的⼩任务,⽽不是三个⼩任务运⾏时长的加和
1.4 案例
1. 多线程提⾼效率
2. cpu占⽤100%
2. Java线程
2.1 创建和运⾏线程
1. Thread
//创建线程对象
Thread t = new Thread(){
public void run(){
//要执⾏的任务
}
};
//启动线程
t.start();
//构造⽅法的参数是给线程指定名字,推荐
Thread t1 = new Thread("t1"){
@Override
// run ⽅法内实现了要执⾏的任务
public void run(){
log.debug("hello");
}
};
t1.start();
2. Runnable
Thread是Runnable的静态代理
Runnable runnable = new Runnable(){
public void run(){
//要执⾏的任务
}
};
//创建线程对象
Thread t = new Thread( runnable );
//启动线程
t.start();
// 创建任务对象
Runnable task2 = new Runnable() {
@Override
public void run() {
log.debug("hello");
}
};
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();
//lambda表达式
//创建任务对象
Runnable task2 =()-> log.debug("hello");
//参数1是任务对象;参数2是线程名字,推荐
Thread t2 = new Thread(task2,"t2");
t2.start();
3. FutureTask
FutureTask 能够接收 Callable 类型的参数,⽤来处理有返回结果的情况
//创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(()->{
log.debug("hello");
return100;
});
//参数1是任务对象;参数2是线程名字,推荐
new Thread(task3,"t3").start();
//主线程阻塞,同步等待 task 执⾏完毕的结果
Integer result = ();
log.debug("结果是:{}", result);
2.2 查看线程运⾏的⽅法
Windows
ps-fe 查看所有进程
ps -fT -p 查看某个进程(PID)的所有线程
kill 杀死线程
top 按⼤写H切换是否显⽰线程
top -H -p查看某个进程(PID)的所有线程
Java
jps 查看所有java进程
jstack 查看某个 Java 进程(PID)的所有线程状态
jconsole 来查看某个 Java 进程中线程的运⾏情况(图形界⾯)
2.3 线程运⾏原理
线程栈与栈帧
Java Virtual Machine Stacks (Java 虚拟机栈)
JVM 中由程序计数器、堆、栈、⽅法区所组成,其中栈内存是给谁⽤的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配⼀块栈内存。
每个线程拥有⼀个栈,线程⽅法就是栈帧,栈是线程私有的
每个栈由多个栈帧(Frame)组成,对应着每次⽅法调⽤时所占⽤的内存
每个线程只能有⼀个活动栈帧,对应着当前正在执⾏的那个⽅法
线程上下⽂切换
线程的上下⽂是当前线程运⾏的所有内容
导致线程切换的原因:
线程的cpu时间⽚⽤完
垃圾回收
有更⾼优先级的线程抢占
线程⾃⼰调⽤了 sleep、yield、wait、join、park、synchronized、lock 等⽅法
当 Context Switch 发⽣时,需要由操作系统保存当前线程的状态,并恢复另⼀个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作⽤是记住下⼀条 jvm 指令的执⾏地址,是线程私有的
状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
Context Switch 频繁发⽣会影响性能(并不是线程越多越好)
2.4 线程⽅法
⽅法名static功能说明注意
start()启动⼀个新线 程,在新的线程 运⾏ run ⽅法 中
的代码
start ⽅法只是让线程进⼊就绪,⾥⾯代码不⼀定⽴刻 运⾏(CPU 的时
间⽚还没分给它)。每个线程对象的 start⽅法只能调⽤⼀次,如果调⽤
了多次会出现 IllegalThreadStateException
run()新线程启动后调⽤的⽅法如果在构造 Thread 对象时传递了 Runnable 参数,则 线程启动后会调⽤ Runnable 中的 run ⽅法,否则默 认不执⾏任何操作。但可以创建Thread 的⼦类对象, 来覆盖默认⾏为
join()
等待线程运⾏结 束,多进程之间需要通信的时候,当前线程使⽤join()等待其他线程执⾏完毕并
传递结果
join(long n)等待线程运⾏结 束,最多等待 n 毫秒
getId()获取线程长整型 的 id id 唯⼀getName()获取线程名
getName(String)修改线程名
getPriority()获取线程优先级
setPriority()修改线程优先级java中规定线程优先级是1~10 的整数,较⼤的优先级 能提⾼该线程被
CPU 调度的机率
getState()获取线程状态Java 中线程状态是⽤ 6 个 enum 表⽰,分别为: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
isInterrupted()判断是否被打 断,不会清除打断标记isAlive()线程是否存活
interrupt()static打断线程
上海传智播客黑马程序员如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出InterruptedException,并清除打断标 记 ;如果打断的正在运⾏的线程,则会设置 打断标 记 ;park 的线程被打断,也会设置 打断标记
interrupted()static判断当前线程是 否被打断会清除 打断标记currentThread()static获取当前正在执 ⾏的线程
sleep(long n)static
让当前执⾏的线 程休眠n毫秒, 休眠时让出cpu 的时间⽚给其它 线程(sleep时不会释放
锁,报锁睡觉)
yield()static 提⽰线程调度器 让出当前线程对 CPU的使⽤,(礼让其他线程,当前线程让出cpu,进⼊就绪状态,与其他就绪的线程公平竞争下⼀cpu)
⽅法名static功能说明注意
run与start
直接调⽤ run 是在主线程中执⾏了 run,没有启动新的线程
使⽤ start 是启动新的线程,通过新的线程间接执⾏ run 中的代码
sleep
调⽤ sleep 会让当前线程从 Running 进⼊ Timed Waiting 状态(阻塞)
sleep不会释放锁
.
其它线程可以使⽤ interrupt ⽅法打断正在睡眠的线程,这时 sleep ⽅法会抛出 InterruptedException 睡眠结束后的线程未必会⽴刻得到执⾏
. 建议⽤ TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
yield
. 调⽤ yield 会让当前线程从 Running 进⼊ Runnable 就绪状态,然后与其他线程同时回到出发点,竞争cpu 具体的实现依赖于操作系统的任务调度器
join
join合并线程,待此线程执⾏完毕后,再执⾏其他线程,其他线程阻塞
可以想象成插队
interrupt
1. 打断sleep、join、wait的线程
这⼏个⽅法都会让线程进⼊阻塞状态
打断 sleep 的线程, 会清空打断状态,即打断状态为false
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论