当前位置:网站首页>多线程 (进阶+初阶)
多线程 (进阶+初阶)
2022-08-09 02:38:00 【不会写代码_】
文章目录
一、进程和线程是什么
1.1程序


1.2端口号和PID

1.3进程和线程


有进程实现并发编程为什么还要使用线程?
虽然多进程也能实现并发编程, 但是线程比进程更轻量.
创建线程比创建进程更快.
销毁线程比销毁进程更快.
调度线程比调度进程更快.
两者区别(面试重点)


Tset就是进程,main是主线程,此时多余的一个进程java.exe是idea自己
总结:
1.4串行、并行、并发

二、Java.lang.Thread
2.1第一个多线程代码
注意事项:
1.启动线程是start方法,不是run方法。通过start方法将线程启动以后,每个线程自动执行自己的run方法
2.Thread-0这些线程名字是默认的,可以修改
3.这四个线程同时执行,互不影响

问题1
主方法明明是在3个线程之后写的,并且每个线程run方法是死循环,为什么main方法是第一个打印的?
答:因为是并发执行,每个线程的执行互不影响,才造成这样的执行效果
问题2

main方法会打印,但是需要等m1m2m3方法调用结束才可以,但是m1的run方法是个死循环,所以只会一直打印m1的线程名字
2.2 jconsole命令

2.3创建线程的四种方法
继承Thread类
// 定义一个Thread类,相当于一个线程的模板
class MyThread01 extends Thread {
// 重写run方法// run方法描述的是线程要执行的具体任务@Overridepublic void run() {
System.out.println("hello, thread.");
}
}
/** * 继承Thread类并重写run方法创建一个线程 * @author rose * @created 2022-06-20 */
public class Thread_demo01 {
public static void main(String[] args) {
// 实例化一个线程对象
MyThread01 t = new MyThread01();
// 真正的去申请系统线程,参与CPU调度
t.start();
}
}


覆写Runnable接口
// 创建一个Runnable的实现类,并实现run方法
// Runnable主要描述的是线程的任务
class MyRunnable01 implements Runnable {
@Overridepublic void run() {
System.out.println("hello, thread.");
}
}
/** * 通过继承Runnable接口并实现run方法 * @author rose * @created 2022-06-20 */
public class Thread_demo02 {
public static void main(String[] args) {
// 实例化Runnable对象
MyRunnable01 runnable01 = new MyRunnable01();
// 实例化线程对象并绑定任务
Thread t = new Thread(runnable01);
// 真正的去申请系统线程参与CPU调度
t.start();
}
}
首先要先创建线程任务对象,才能创建线程对象,并且在创建线程对象时将线程任务对象传入进去。

两种创健线程方式的不同写法
匿名内部类
/** * 通过Thread匿名内部类的方法创建一个线程 * @author rose * @created 2022-06-20 */
public class Thread_demo03 {
public static void main(String[] args) {
Thread t = new Thread(){
// 指定线程任务
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
// 真正的申请系统线程参与CPU调度
t.start();
}
}

/** * 通过Runnable匿名内部类创建一个线程 * @author rose * @created 2022-06-20 */
public class Thread_demo04 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
// 指定线程的任务
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
// 申请系统线程参与CPU调度
t.start();
}
}

Lambda表达式
/** * 通过Lambda表达式的方式创建一个线程 * @author rose * @created 2022-06-20 */
public class Thread_demo05 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
// 指定任务:任务是循环打印当前线程名
while (true) {
System.out.println(Thread.currentThread().getName());
try {
// 休眠1000ms
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 申请系统线程参与CPU调度
t.start();
}
}


2.4线程和串行时间对比

练习

正解:可能先打印1也可能先打印2,具体先调度子线程输出还是先调度主线程输出由系统决定。
至于为什么多次试验都是 21的结果:


面试小问题
如果一个线程连续使用start()方法启动会怎么样?
三、Thread常用方法
3.1构造方法

注意事项:第一种方法一般搭配子类使用,因为Thread类中的run方法为空,什么都没有
3.2Thread类的核心属性


3.3中断线程的两种方法
注意Thread.sleep是个静态方法,在哪个线程用就在哪个线程生效

这里不管使用成员方法还是静态方法,只要捕捉到中断异常,线程的中断状态会被清除,这里线程被终止的原因是因为加了break语句,否则不加break语句线程会改变状态继续执行
3.4线程中断通知(重点)

下图中使用的是类方法判断线程是否中断:Thread.interrupted()
它的作用是当线程被中断时threa.interrupt()之后,将中断状态ture置为false
下图中使用的是对象方法判断线程是否中断:Thread.currentThread().interrupted()
它的作用是当线程被中断时threa.interrupt()之后,不改变中断状态,仅仅只是查看当前线程是否中断,不做修改
红色方框查看线程是否为中断状态
橙色方框是修改线程为中断状态
3.5等待另一个线程

加了join()方法相当于图中t1,t2,主线程三个线程变成了串行,而不是并行
join()方法延伸

t1.start();
// 主线程死等t1,直到t1执行结束主线程再恢复执行
t1.join();
// 此时走到这里,t1线程已经执行结束,再启动t2线程
t2.start();
// 在哪调用别的线程的join方法,阻塞的是调用join的线程
// main -> 调用t2.join() 阻塞主线程,直到t2完全执行结束再恢复主线程的执行
// 主线程只等t2 2000ms - 2s,若t2在2s之内还没结束,主线程就会恢复执行
t2.join(2000);
// t2线程也执行结束了,继续执行主线程
System.out.println("开始学习JavaEE");
System.out.println(Thread.currentThread().getName());
}
}
3.6获取当前线程对象和休眠当前线程对象

3.7线程的状态


New状态到Runnable状态只需要start()方法
Runnable就两个状态:一个Ready和Running
超时等待时间到了就会还原状态:还原成ready状态
NEW 和 RUNNABLE状态


三种阻塞状态


yield()方法
就绪态:当前状态随时可能被CPU调度的状态
public class YieldTest {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName());
// 春鹏线程就会让出CPU,进入就绪态,等待被CPU继续调度
Thread.yield();
}
},"春鹏线程");
t1.start();
Thread t2 = new Thread(() -> {
while (true) {
System.out.println(Thread.currentThread().getName());
}
},"云鹏线程");
t2.start();
}
}

线程状态意义

四、线程安全
4.1线程不安全概念
比如t1和t2各自两个线程的run方法是累加5w的数值,那么最终两个线程应该相加得到10w,但是图中并发执行t1和t2线程却每次得到结果都不同。


4.2Java的内存模型-JMM

线程的局部变量:例如方法中的变量
4.3线程安全的三大特性
原子性
a+=10对应的三个原子性操作:先读取a的值,再执行a+10,最后将结果赋值给a
可见性

count++的原子性操作有三步,所以可能一个线程中还没进行完三步的操作,另一个线程就去读取count的值,可能导致每次线程读取的count值不是执行count++后正确的数值
举个例子:这个例子演示的是不可见性,但不一定这个数值一定是图中所说的情况造成的,只是其中的一种可能性。


第二种可能性:有很多种可能性,这里只是列举一种,总之最后的答案一定不是10w。
相当于本来计划+2次,但最终只+了1次,本来t1已经将count=1写回了主内存,但是t2也让count=1写回了主内存,最后count的值还是1,应该是2才对。

类似火车的售票系统:
客户A在买票时,发现还有一张票,当他下订单时,在主内存中nums=1-1=0没票了,但此时恰好客户B和A是同时准备购买票,但是此时nums=0还没有写回主内存,导致B也买了这张票(超卖现象)。

关于内存的问题


指令重排


总结(面试重点)

4.4 synchronize关键字
针对同一个对象才会导致线程安全问题,这里t1和t2处理的两个不同对象,最后结果都是5w

monitor lock对象锁

mutex lock互斥(synchronize第一个特性)



不管几个线程对这个对象进行处理,同一时刻只有一个线程拿到这个对象的锁,拿到这个锁就会执行increase()方法,另外线程就处于等待阻塞状态,哪个线程先拿到对象的锁不确定。
关于互斥的深入理解:到底有没有互斥关系,就看这些线程锁的是不是同一个对象


代码块刷新内存(第二个特性)

上锁操作和单线程操作区别
比如ABC三个同学都想上D老师的课,但是D老师每次只能给一个学生上课(这个上课就相当于一个方法),这就是单线程。但是ABC三个同学能同时获取D老师的VX(这也是一个方法)就可以多线程执行这个方法。
可重入(第三个特性,面试重点)

可重入的概念:
关于为什么加两次锁的原因:在increase1同步代码块里再加锁不是人为决定的
至于加不加synchronized的问题:

4.5 synchronized修饰类中的成员方法



4.6 synchronized修饰类中的静态方法



4.7 synchronized修饰当前的对象(难点)
相当于多线程优化,只把一个方法中部分代码锁住,执行时间就可以缩短
当不同的对象调用increase4()方法就不构成互斥了
4.8 synchronized修饰当前class对象(难点)

之前我们说的不构成互斥现象是因为各个线程调用的是不同对象里的同一个方法,现在构成互斥是因为虽然也是不同的对象调用这个该方法,但是synchronized锁的class是对象,代表每次只能有一个对象执行这个方法,因此,不管是几个对象,每个对象之间都构成互斥。

4.9线程安全类(了解即可)




五、volatile关键字
5.1保证可见性

如果flag之前不加volatile,当启动t1和t2线程以后,t1线程是一个死循环,t2线程可以输入数值改变flag的值使t1线程终止,但是当我们输入之后,t1还是没有终止。原因就是t1线程一直读取的是自己工作内存中的falg=0这个数值,没有读取到t2对flag更新的数值。但是加了volatile就会立刻退出循环,这就是保证了共享变量的可见性
注意事项:sychronized也能保证可见性但两者不等同
5.2 内存屏障
cpu在不影响结果的情况下对指令进行重排

volatile保证可见性为什么不保证原子性?
原子性在cpu执行的过程中一个或多个操作不能被其它线程中断。例如int a赋值操作是cpu天然的原子性,要让天生不原子性的操作
volatile和sleep区别

六、线程间等待与唤醒机制

6.1 wait()等待


6.2 notify()唤醒
随机唤醒:不一定先等待的就先唤醒


调用notify方法唤醒在等待的线程,一定要等notify方法中同步代码块里执行完毕才可以执行被唤醒的线程。
6.3等待队列 阻塞队列

每一个对象都有一个等待队列和阻塞队列,例如图中t3先获取到锁,所以t1和t2首先进入阻塞队列,t3调用wait方法就释放锁,t1和t2就去竞争锁,t3就会进入等待队列。当t1 t2 t3都进入等待队列,调用notifyall方法,则三个线程都被唤醒,但不是立即调度,从等待队列同时将三个队列置入阻塞队列去竞争锁

面试题:sleep()和wait()的区别

七、单例模式

7.1饿汉式单例
不管外部调用不掉用这个对象,只要类加载到JVM,唯一对象就会产生





7.2懒汉式单例

7.3饿汉和懒汉的线程安全问题
饿汉式是天然的线程安全,每个线程调用的都是同一个对象,但是懒汉式就不一样,因为每个线程开始时看到的内部对象都为空,可能每个线程都会产生一个对象:
解决懒汉式线程安全问题
1.直接在方法上加锁
2.优化 double - check
下图的写法不可取,因为其它线程也会卡在相同的位置,等待第一个线程执行结束,还是会从相同的位置产生一个新对象,就无法保证一个对象的原则

3.使用volatile关键字
volatile保证防止指令重排


八、阻塞队列


8.1生产消费者模型


8.2JDK中阻塞队列BlockingQueue

阻塞队列的大小一般由构造方法传入
8.3定时器常用方法

九、线程池
9.1线程池的作用

减少创建线程的时间和开销,只需要从系统中取出任务(run方法)就可以执行
9.2JDK线程池使用

9.3Executors线程池工具类
四种创建线程池方法



9.4ThreadPoolExector核心参数


动态缓存池:核心线程为0,每当有新任务进来都是临时创建线程
单线程池:
固定大小延迟池:
单线程池的意义

9.5线程池工作流程

十、常用锁的策略
10.1悲观锁 乐观锁

10.2乐观锁常用实现
开始时线程1和2先将主内存中的值100和版本号1读入自己各自的工作内存



如果尝试重新写回主内存成功就变为balance=30 version=3
10.3读写锁

10.4重量级锁和轻量级锁


10.5公平锁和非公平锁

边栏推荐
- YOLOV1详解——Pytorch版
- 2022 Eye Health Brand Franchise Exhibition, Beijing Vision Care Exhibition, China Ophthalmology Technology Summit
- 最新工业界推荐系统数据集-召回排序模型原理、结构及代码实战整理分享
- 用DFS解决最终幻想13-2时钟谜题
- "Lonely Walking on the Moon": Two choices of Duguyue, let a "middleman" become a big hero
- gpio子系统和pinctrl子系统(上)
- online schema change and create index
- 【AspNetCore】实现JWT(使用Microsoft.AspNetCore.Authentication.JwtBearer)
- 旋转霓虹圆圈
- 物联网未来:未来五年的预期
猜你喜欢

ApiFile配置环境

为什么应用程序依赖关系映射对于云迁移至关重要

帮助安全红队取得成功的11条建议

eladmin container deployment super detailed process

The building had been registry cluster, load balancing

Jenkins configuration nail notification

Processing Point Clouds

评估深度学习模型的指标:混淆矩阵、准确率、精确率和召回率

MT4/MQL4 Getting Started to Mastering EA Tutorial Lesson 1 - MQL Language Common Functions (1) OrderSend() Function

带你做接口测试从零到第一条用例 总结
随机推荐
数字 05 verilog&vivado2018.2零散笔记
【HNUMSC】C language second lecture
用DFS解决最终幻想13-2时钟谜题
普通人如何增加收入
从0开始搭建自动化测试框架之PO分层架构
Likou Brush Question Record 1.5-----367. Valid perfect squares
DataGridView在多线程中出现大红叉
数字 07 verilog仿真实例
基于NLP的智能问答系统核心技术
Likou Brush Question Record--Common Functions
Jenkins environment deployment, (packaging, publishing, deployment, automated testing)
历史最全DL相关书籍、课程、视频、论文、数据集、会议、框架和工具整理分享
全文翻译:Multimodal Neural Networks: RGB-D for Segmantic Segmentation and Object Detection
【电商运营】不知道怎么做网站优化?这里有你需要知道的一切!
Pytest+request+Allure实现接口自动化框架
DSP28379学习笔记 (一)——GPIO基本操作
Summary of pytorch related knowledge points
C#计算两个时间相差多少天、时、分、秒
20220524搜索和排序:搜索二维矩阵II
搭建Eureka注册中心集群 ,实现负载均衡