基础篇-多线程
作者:互联网
Thread 线程
电脑中多个软件运行工作,看似是同时执行,其实是“多线程”在交替执行任务。
“多线程”像是道路,“网络数据”是道路运输的资源,道路可能发生阻塞,也可能运输资源速度慢,会出现问题。开发者需要了解并解决问题。
程序:应用程序(编写的代码,静态的)
进程 :运行的应用程序(运行的代码,动态的)
线程:进程的实现,保存程序运行成为进程。进程中包含多个线程,是一对多的关系。
并发与并行的关系
并发:开启程序多线程同时运行,对数据资源的操作顺序不同(单核CPU)
一杯奶茶一根吸管,并行操作,需要排队,你喝一口停下来干其他事,你大哥喝一口便停下来,你回来再喝一口,然后去干其他事,你大哥又来喝一口
进程并发关系人类无法用肉眼识别,计算机运行速度纳米级别,产生同时执行的错觉
计算机一定是多线程,即使你不创建线程也会有多个线程,后台主线程,gc线程等
并行:多线程同时执行程序运行,对数据资源同一时刻获(多核CPU)
一杯奶茶两根吸管,并发操作 无需排队,你和你大哥同时各握着一根吸管猛吸奶茶
<!-- 使用代码查看CPU个数 -->
Runtime runtim = Runtime.getRuntime();
int cpuNums = runtime.availableProcessors();
多线程的应用:
电影播放时,线程图像 线程声音 线程字幕,多线程保证用户的体验。
运行程序时,JVM垃圾回收对堆中垃圾对象清理,保证内存利用。
所有的线程是由操作系统分配的,所以多线程跟操作系统关系密切。
操作系统为进程来分配足够的线程以保证运行,并且记录线程执行进程的进度。
为什么使用多线程?
吞吐量: 普通的Web程序是单层面请求,一个请求一个线程或多个请求一个线程
伸缩性: 没用充分发挥CPU的多核性能,将任务排列到一个线程上
main线程
main线程是“主线程”,是程序执行的必要条件 ,JVM系统的入口
多线程的核心
当开启了其他线程,多线程的情况,而其他线程的核心在start0()方法
Java中创建线程的方式
- 继承Thread类:
优点:操作简单
缺点:避免继承出现
原理:
重写Runable接口的run方法,调用srart底层核心为start0的native本地方法
start0()方法执行由CPU调用不同的操作系统利用不同的算法【核心】
-
实现Runable接口:
实现Runable接口run()方法,创建Thread对象,并通过Thread构造器中的静态代理模式将实现Runable接口的对象传入
优点:避免继承,Thread多线程共享一个Runable实现类的资源,无需数据资源static关键字 -
实现Callable接口:
实现Callable接口的call方法,与Runable的run方法不同的是call方法有返回值
优点:有返回值
缺点:必须有返回值 -
线程池方式:
创建Executors类,提供一系列创建线程池的静态方法,返回值实现ExcutorService接口,通过实现ExcutorService接口调用submit方法将传入Callable或者Runable的实现类,线程执行是实现类的run方法
优点:自动化装配,节省资源,资源循环利用,利于管理
什么是线程体
run方法
直接执行run()方法 和 start()执行run()方法的区别
直接执行run()方法将是main线程进行执行,而使用start()则是创建的其他线程去执行
多线程安全问题
同一份资源,可能发生资源抢夺问题 ,需要加入并发数据资源控制,对于代码中的作用域问题,多线程会操作一份资源,安全问题将会发生
多线程执行优先权
每个线程都有优先权可以设置,实际线程在运行中的执行权由CPU调动
一个进程开辟了多个线程,线程的执行权重由CPU调度器安排调度,人为不得干预
守护线程&&用户线程
Thread使用到的设计者模式
- 静态代理
由于Tread方法实现了Runnable接口。
选择实现Runnable接口的方式创建线程,把Runable的实现对象丢进Thread构造方法,将会静态代理
,代理模式与适配器模式不同:适配器模式用于衔接,代理模式用于方法的增强,核心Proxy类用于扩展
公平锁与非公平锁
Lock接口比synchronized块的优势是什么?
- Lock手动锁,更加灵活的释放锁lock方法:加锁,unlock方法:释放锁
- Lock公平锁,多线程先进先出的顺序
- synchronized非公平锁,多线程抢占机制
- Lock接口是synchronized升级版,Lock接口实现类即提供公平锁和非公平锁
注意:非公平锁大部分情况效率高
多线程解决同步问题方式?
一、分析问题出现的原因
多线程访问共享数据资源,执行方法结束后结果出现错误,共享资源没有被严谨上锁,导致数据被其他线程操作。
- 多线程执行速度快,进入同一方法块中,避开了逻辑判断
- 多线程之前的线程进行等待,没有更改逻辑判断数据,后面的线程也跟着进来
二、解决多线程问题的方法
- 同步代码块
- 同步方法 锁任何对象 /静态同步方法(由于静态不能访问非静态所以锁class字节码文件)
- Lock手动锁方式,使用Lock锁的实现类,并使用unlock解锁
Tread类实现了Runable接口
- 当一个类继承了Thread类重写Run方法,就可以多线程使用start函数()
理据1 关于run()函数在Runable接口中,由Tread实现run()函数,你继承Thread类进行重写run()函数
理据2 关于start()函数会开启线程进行执行,否则不会开启,依然是Main线程执行
运行代码 进程占用内存 开启Main线程 start()函数开启Thread-Nums线程
注意:Main线程不会与执行start()函数的多线程阻塞,Main线程仅执行start()函数开启线程,会继续从上至下执行,不等其他线程,猛烈的向前冲。
若没有使用start()方法,则直接使用run()方法则会发生Main完全执行,因为此刻run()方法为普通方法
理据: Debug步入start()函数,步入start0()函数是JVM本地方法,底层是c/c++实现,实际是start0()产生了多线程效果,开启线程核心并不是run
会出现什么情况?
Main线程与Thread—Nums线程并发或并行
结束时会出现什么情况
即使Main线程结束退出,Thread-Nums依然会执行完完任务,全部线程完成完毕进程结束
注意:避免Main线程直接调用run方法
主线程一般退出最快的,谁完成谁的事谁退出,悟空使用猴毛,分身干分身的事,悟空干悟空的事
所有多线程都是子线程开启的
直接实现Runable
但是Runable中没有start方法
怎么办?
将聚合关系修改为依赖关系
创建Thread对象,将线程对象传进去,调用start()函数
这是什么鬼操作?这里用到了设计者模式-静态代理 跑腿人
设计者模式利用接口实现多态,动态绑定
实现Runable避免了单继承的问题,适合多个线程共享一个资源。【建议】
此后怎讲:继承Thread后每创建一个多线程类都将开启一个run实现任务和Thread继承
若继承Thread类 使用静态 让多个对象 仅由一份变量 为票数
此刻会出现多个线程同时执行–i情况
若实现Runable接口 多个Thread类将共享一个 多线程对象 无需使用静态
此刻会出现多个线程同时执行–i情况
注意:最坏情况,速度过快全部线程都执行一下进入判断,尝试让其睡眠可以让其无法快速进入
- 自然销毁:完成任务后销毁
- 通知销毁:经过Main线程主人的通知销毁,通过setting修改逻辑boolean变量,并且可以Main主人进行睡眠,睡醒就会通知线程销毁
Runable中的run方法【核心】
Thread对象【核心】
-
setName设置线程别名
-
getName获取线程别名
-
start线程开始执行:调用底层start0()方法
-
run执行线程对象简单的run方法
-
setPriority设置优先级
-
getPriority获取优先级
-
sleep睡眠
-
interrupt中断线程(不是中止结束,比如休眠被唤醒)
-
yield线程尝试礼让,调用自己的(主要有CPU决定,线程资源越紧张概率越高)
我问业务员是否把资源让另一个紧急情况的人,由业务员决定,业务员资源充足,则都同时进行服务,业务员发现资源不充足,由你提出了将资源让给需要的人,这时业务员才会专注于帮助需要的人 -
join线程插队,调用别人的(尝试让其他线程先执行任务,主动让其他线程插入前面完成任务,一定成功)
我和其他人排队到业务厅,但某事某刻我选择主动让其他人先插队到我的前面先完成业务
用户线程:Main线程与子线程A同时执行,线程A执行生命周期结束,线程A不受印象继续执行
守护线程:但是此刻,子线程A成为守护线程,监测Main线程的生命周期,若Main线程结束,B就将会缓速结束
常见守护线程:垃圾回收机制
APi enum
New进入创建状态start()
Runable就绪状态(等待CPU分配)Ready/运行状态Running (或手动yield)
sleep wait join lock 运行状态尝试获取锁,进入Blocked阻塞状态,监测锁,又回到Ruable状态,可能是就绪状态,也可以直接运行,取决于CPU调度器
wait join lock让其他其他线程先获取资源进入Wating状态进行等待状态,一些方法需要唤醒
睡眠进入TimedWaiting延时等待状态
Teminate终止结束
一些数据不允许多个线程同时访问,使用同步Synchronized保证同一时刻,数据只能有一个线程进行访问操作
厕所只能一个人进入,进入后把门关闭
同步代码块
对对象加锁
同步方法
对方法加锁
互斥锁:每个线程都有一个互斥锁的标记
获取不到锁进入阻塞,就像高速路的车拥挤到收费站,当然不是越多收费站越好,多了速度会变慢
同步方法:默认锁在this对象
同步代码块:锁在this对象 指定缩小范围【优选】
多个线程:可以是任意对象,必须是任意同一个对象,this或object
静态同步方法:锁不再this,锁将加载这个类Class对象上
静态方法里用同步代码块:就要对这个类的Class对象上锁
1非静态同步方法默认锁this对象
2静态同步方法默认锁当前类.class对象
3多线程的锁为同一个对象,保证共享对象【重点】
一个厕所3个门
A锁B资源,A拿不到自己的资源无法执行
B锁A资源,B拿不到自己的资源无法执行
1.执行完程序自行释放
2.遇见break,return直接退出释放
3.出现异常,直接退出释放
4.wait等待
便秘一下子
不会释放锁
sleep
上厕所睡着了
yieId
Running状态进行Ready状态没有退出Runable状态
suspend挂起【不建议使用】
Running状态进行Ready状态没有退出Runable状态
标签:Runable,run,Thread,基础,线程,多线程,方法 来源: https://blog.csdn.net/weixin_51705997/article/details/119872483