一、前言与预备知识
1.1为啥学
学习并发编程可能你正常工作几年都难用到,但是 想要往更好的方向走 这玩意是必须要学的
除非你想一辈子就在个小公司crud 很多中间件基本都用到并发编程,你想要了解它们的底层,得 学吧 然后这玩意尽管大部分公司工作中用的少 但是中高级以上级别面试可是必问的
1.2 进程与线程的概念
一个父 一个子 子是最小单位


并发
并行

1.3 同步与异步的概念
如下两图 在同一个线程里
图一 先来后到 代码按顺序走完就是 同步 图二 代码在两个线程里 自己跑自己的 就是异步




记住:单核cpu下,多线程并不能提升效率,反而会增加线程之间的切换 多核的情况下,可以在不影响运行结果的情况下 想办法将任务拆分 交由多个线程执行

二、线程创建的几种方式
2.1 Thread

2.2 Runnable 配合 Thread
可以看Thread的源码中的run方法如下 如果target不为空 则执行 target的run方法 而target对象
就是 Runnable 的实例,在 Thread构造时可传入

优先组合 然后才是继承 可以解耦合

2.3 FutureTask 配合 Thread
可以看到 FutureTask 是 Runnable子类

然后它里面是有个 Callable对象 调用thread的run方法时 会调用 FutureTask里的 Callable实例的call方法 然后放到 result中

调用get方法时 get方法内会一直判断 当前任务执行的状态 如果是 已完成 则 得到结果 强转为泛型 并返回 否则阻塞住

三、进程与线程运行情况 及 查看 与 杀死 相关命令
3.1 windows 中

一般上面tasklist用的不多 我们可以使用jps查看java进程 然taskkill杀死

![]()
或者 在任务管理器中找到,然后右键 结束任务

3.2 linux 中
3.2.1命令概览

一般 用管道符过滤 | grep 来查看java进程
![]()

找到后 kill pid 杀死对应进程
3.2.2 top命令实时查看进程线程的运行时间及cpu占比等信息
top命令执行后能看到当前所有运行的进程 及 cpu 的消耗情况

我们一般用法就是 先找出java相关进程

再找到该进程下 对应线程运行情况 top -H -p 102431
这个命令 top 命令可以实时查看 进程线程 的 运行时间及cpu占比
3.2.3 jstack 查看某一时刻 某进程中所有线程的状态及运行信息
如果想要查看当前这一时刻的进程的所有线程更详细信息
可以使用jstack pid

3.3 jconsole图形化界面
jconsole 可以检测linux与windows中java程序运行情况
3.3.1 linux环境下模拟程序运行并添加 监控配置
3.3.2 关闭防火墙才能用本地jconsole连接
3.3.4本地启动jconsole并连接
![]()

3.3.5 jconsole图形界面中查看java线程运行情况
四、线程运行原理
4.1 初步了解线程运行
如下6张图 注意看右上角 左下角 及 右下角
刚开始 运行main方法 做小姐创建一个栈帧 存放 main方法运行的信息 右下角 是当前运行栈帧的参数变量 -》第二张图 运行method1方法时可以看到左下角多了一个栈帧 并且活动栈帧也变成了method1所处栈帧 -》第三张图 运行method2 也与 method1 一样 -》再然后第四张图 method2 运行完后返回到method1末尾 此时 运行method2的栈帧出栈 栈空间被回收 -》图五 也一样 =》直到最后 main方法所处栈帧出栈
总结 : 方法的执行信息是在一个栈内的某个栈帧上的 ,并且一个线程只有一个活动栈帧,为什么是栈呢? 先进后出 符合方法的执行顺序与 栈帧空间chu回收顺序






4.2 深入了解线程运行
线程的运行 大致涉及了下面这些角色:CPU,堆,栈 ,方法区
首先 会执行类加载 将类文件的 二进制字节码文件加载到虚拟机中 存放在方法区 图中是为了好理解,其实存放的是二进制字节码
再然后一个线程被创建 由cpu调度执行 这里比上面多了些信息
程序计数器:下一步 运行二进制字节码的行数 cpu从程序计数器拿到后调度执行
返回地址: 记录当前方法执行完 应该执行的下一行代码的位置
堆:所有new出来的 对象的值都放在堆中进行管理 堆 也是jvm内容部分最为关键的研究对象
锁记录与操作数栈 后面会在 锁部分继续补充 循序渐进
那么 想比与4.1的初步了解,现在我们已知的更深入了解的线程运行的步骤就变成了:首先 一个方法被调用时就被分配一个栈帧 程序计数器指向第一行代码 然后 cpu得到执行的代码的位置 调度执行 执行的初始返回地址就有值了, 执行过程 中 局部变量表内容开始增加 如果局部变量的值是被new出来的,那么 它的值对象 会被放在 堆空间 局部变量表只存放变量名 与 x=10 这类常量信息 当某方法执行完后程序计数器会指向当前方法的返回地址 该方法所处栈帧被弹出,栈帧空间被回收 直到方法运行完

4.3 线程的上下文切换
简单来说 从cpu的某个核从 一个线程的执行 切换到另一个 线程的执行 就叫线程的切换
上下文是啥呢? 就是线程栈内 信息 这些信息在线程执行完之前都会一直保存着 包括 程序计数器,局部变量表等等信息 线程的上下文切换是有性能消耗的 上下文切换的被动主动时机 如下图及更多详细信息如下图

4.4 怎样断点 调试 多线程
新建断点 断点上右键 选中 Thread 就可以在线程模式下 断点调试

如下debug运行后 可以左下角看到 有两个线程 并且他们此时的状态都是运行中
点击任一一个都可以 进入到当前线程执行界面去调试

五、常见的方法
5.1概览
常见的方法概览如下


5.2start 与 run 方法
5.2.1 start 与 run 方法的区别
如下两图是调用 run方法 与 start方法的比较 可以看到控制台 中括号内,直接调用run方法还是在当前main线程内执行,而调用start方法,相当与新开了一个线程然后执行run方法 新开的线程与当前main线程的执行 互不干扰

5.2.2 执行start方法前后 线程的状态
可以看到 状态由 NEW 变成了RUNNABLE new是刚创建出来的状态,runnable是就绪状态可被cpu调度执行
5.2.3 执行两次start方法会?
会报错,同一个线程 start方法 只能调用一次

5.3 sleep 与 yield 方法
sleep 是让当前线程休眠(此时这个休眠是有时限的)因为sleep方法必须传参 ,正常休眠结束后 线程会从timed waiting状态转变为 runnable状态 从图二可以看到休眠状态下的线程被打断后 会抛一个被打断异常 它的状态也是变成了runnable 就绪状态
yield好比是让座,我本身运行着 然后变成 runnable就绪状态 其他同优先级的线程可以占座变成运行状态,如果没有同优先级的线程,那么结果是随机的,可能我还是继续运行 也可能别人运行


5.4线程的优先级

5.5 sleep小应用
当我们在 某个线程的 执行代码内加 while(true) 代码时 一定要记得在里面加 sleep 进行短暂休眠

如下两图 分别是 在 while( true) 代码内 没加sleep 与加了的 cpu占比 区别 ,前者直接快占满


5.6 join方法
5.6.1不带时效的join方法
如下两图 图一没使用join 图二使用了join, r为全局变量 主线程 需要显示他t1线程运行后的r的结果 ,那么就必须要等 t1线程执行完 再 输出 r 而join方法也正是 起到这样的作用 调用join方法的线程 会立马处于第一优先级 执行完这个线程 后 才会继续执行其它线程 图二中 因此主线程中打印的r的结果为 10


join会抢占cpu 然后其他线程如果是timedwaiting状态 那么 没影响 如果是 运行状态,那么就被抢过时间片 然后等着 所以两张图 总执行的时间都是以执行最长时间的那个线程为准 2秒

5.6.2带时效的join方法
没带失效的join就好比是 你开了挂,cpu必须先执行完你才能执行其它线程 ,带了失效的join方法如下图 就比如你这挂有时效 1.5秒这个时间段内 cpu必须执行你 但是 1.5秒过后cpu又是一视同仁

5.7 interrupt方法
调用了 sleep,wait,join等方法 的线程 被打断时 会抛InterruptedExeption 然后isInterrupt标记还是为false 处于这些状态被打断的线程 我们可以在 catch中做处理 如果不做处理 被打断的线程会回到 runnable就绪状态 等待被分配到cpu的时间片后再重新执行

正常运行的线程被打断 时 它不会抛异常 而是isInterrupt被打断标记 被置为true 比如 下图 死循环内 我们 可以判断 isInterrupt的值然后做处理 是结束线程运行还是继续
5.8 两阶段终止 - interrupt
下面两图代码模拟的是一个监控程序 监控程序每次执行前会判断打断标记 如果被打断了则料理后事然后停止监控程序的运行 否则休眠一秒后 执行监控并记录 如果在休眠或者监控记录的过程中 被打断 这个时候会被catch捕获 这时被打断状态仍未false(前面说了) 而线程状态为runnable就绪状态 所以这时要重新打断 就绪状态(非sleep,wait,join)的线程被打断 线程打断标记是会被置为 true的所以下次循环时 会料理后事 然后程序终止



5.9 isInterrupted方法与 interrupted方法
5.9.1 两者异同
如下,这两个方法都可以判断 线程是否被打断 但前者是非静态方法 后者是静态的 然后 前者不会清楚打断标记 后者会清除打断标记 如果你要判断后 清除打断标记就用后者


5.9.2使用park方法时的选择
打断标记为true时会让park方法失效
先看看图一 park方法的作用 如下图 执行park方法后当前方法就被阻塞住了 再看看图二当线程由于调用park阻塞时 调用interrupt打断就可以让该线程继续运行


但是 当 isInterrupt状态为true时 park方法不生效 如图下图一
这时 如果需要park生效就可以使用前面说的interrupted()静态方法 它会在获取到打断标记后将其置为false 因此后面的park才能生效
5.10 不推荐使用的方法
他们都过时了,因为一些弊端 比如 stop()停止线程会导致锁不释放 其它线程获取不到锁就凉凉

5.11 setDiaemon 守护线程
可以理解 Diaemon是辅助程序 其它的线程 都执行完 不管 守护线程咋样 都会结束进程
也相当于运动会的陪跑 参赛者跑完了 比赛就结束 谁管你 陪跑

5.12 统筹规划习题
现在有这样一道题目,有老王跟小王 他们想喝茶了
老王只负责烧水 耗时五秒
小王 负责洗杯子 拿茶叶 泡茶 分别耗时 2,3,2 秒
用代码模拟 怎样让他们最快喝到茶
代码如下 主要用到了sleep 与 join俩方法 最快时间就是 烧水加泡茶的时间 大概七秒
package com.robert.concurrent.test;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
@Slf4j(topic = "c.test1")
public class Test1 {
public static Thread threadXiaoWang = null;
public static Thread threadLaoWang = null;
public static long start;
public static void main(String[] args) throws InterruptedException {
start = System.currentTimeMillis();
threadXiaoWang = new Thread(() -> {
try {
method1();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threadLaoWang = new Thread(() -> {
try {
method2();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threadXiaoWang.start();
threadLaoWang.start();
}
private static void method1() throws InterruptedException {
log.info("洗杯子");
TimeUnit.SECONDS.sleep(3);
log.info("洗完杯子");
log.info("拿茶叶");
TimeUnit.SECONDS.sleep(2);
log.info("拿到茶叶");
threadLaoWang.join();
log.info("泡茶");
TimeUnit.SECONDS.sleep(2);
log.info("泡完茶了,总共耗时{} ms",System.currentTimeMillis() - start );
}
private static void method2() throws InterruptedException {
log.info("烧水");
TimeUnit.SECONDS.sleep(5);
log.info("烧完水了");
}
}


六、线程状态
6.1 操作系统层面

6.2 java API层面



6.3 代码
线程刚new出来 NEW状态
线程处于 就绪,运行或者IO阻塞时 都是runnable状态
因为执行后 后面主线程有五秒休眠 所以那时候 t3是终止 terminated状态
timeed waiting 状态
join线程占据cpu时间片 所以 t5等他执行完才能执行 是 waiting 等待状态
因为锁被 t4持有一直没释放 所以 t6是阻塞状态
七、共享模型之管程
7.1 共享问题

如下为代码演示

原因 看似 count ++只有一行代码 但是 它在jvm层面 是由如下4步组成 所以 当我count为5时
准备 加1 执行到一半 被 减一的线程抢占cpu 然后那边开始减一 正常加一减一结果应该还是5,
但是由于 先减一 此时 count变为 4 而 加一操作那边count的值还是5然后进行加一 最终 结果count就变为6了


7.1 临界区
以上问题 我们一般描述为 在临界区 出现静态条件 导致

7.2 syncronized
7.2.1 syncronized初步使用
这是个啥东西呢? 同步的意思 也就是常说的锁的一种
如下代码 与运行结果 为0
@Slf4j(topic = "c.test1")
public class Test1 {
public static Thread threadXiaoWang = null;
public static Thread threadLaoWang = null;
public static long count;
public static Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
threadXiaoWang = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (lock){
count--;
}
}
});
threadLaoWang = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
synchronized (lock){
count++;
}
}
});
threadXiaoWang.start();
threadLaoWang.start();
TimeUnit.SECONDS.sleep(3);
log.info("计算后的count的值为{}",count);
}
}
得到正确结果

那为啥加了syncronized就好使了呢?
简单来说 它其实就是在锁对象内做了某个标记 标记里面有执行当前 syncronized代码块的 线程id,若是代码块内的代码没有运行完 被别的线程抢占到了cpu那么 判断 当前线程与锁对象内的线程是不是同一个 不是就被阻塞 然后 运行完代码块后 锁对象内的 信息会被清除 这样两个线程又可以公平竞争了 这样就保证了 程序最终得到正确的结果
7.2.2 syncronized问题思考
上面简单说了syncronized 的原理 后面还会继续补充 那么就下面几个问题 做下思考

问题1:放在for循环外面
这样也能保证正确结果 但是 程序运行情况就是 先 做5000次自增 再做5000次自减或者相反
问题2:锁不同对象
这样起不到锁的作用, 最终结果还是错误结果 不是 0 因为锁对象上有持有线程信息
问题2:只有一个线程内加了syncronized锁
起不到锁的作用 只有一人持有 不影响另一方的执行 与不加锁情况一样
syncronized加在普通方法上

syncronized加在静态方法上

加syncronized 好比 在门口排队
7.3 线程安全分析
7.3.1线程八锁
以下八张图 为 线程八锁 想想控制台的打印结果吧,这里就不贴答案了
提示: 锁住同一对象才 有用 锁住不同对象等于没锁
7.3.2 变量共享是否产生线程安全问题
多个线程共享同一变量会导致线程安全 不共享则不会 (后面会引入原子类解决这一问题)
7.3.3 线程安全分析习题
判断以下示例是否线程安全
1.不安全 ,安全 ,安全 ,不安全 ,不安全
String类不可变类 Date虽然声明为final但是
其内成员变量被共享 可能产生线程安全问题
2. 不安全
Servlet类单例 所以userService实例被共享 然后count变量 作为共享变量 可能产生线程安全问题
3.不安全
MyAspect没加 scope注解 所以默认单例 然后 start作为成员变量被共享,
当before方法被多个线程调用时 start 变量 在after方法内可能得到错误的值 所以线程不安全
线程安全
线程不安全 与上面类似 conn对象 被多个线程共享 可能一个线程 得到 conn连接了,此时时间片被另一个抢过去 将 conn可能直接关闭
线程安全 但是写法不合适 userDao这种对象一个就够了 Connection对象一般声明为局部变量 避免 共享后其它线程的修改
不安全 sdf对象被传入某个方法中 方法中的代码未知 所以 可能存在多个线程操作的情况



























最低0.47元/天 解锁文章

76

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



