论坛
潜水/灌水快乐,沉淀知识,认识更多同行。
ToB圈子
加入IT圈,遇到更多同好之人。
朋友圈
看朋友圈动态,了解ToB世界。
ToB门户
了解全球最新的ToB事件
博客
Blog
排行榜
Ranklist
文库
业界最专业的IT文库,上传资料也可以赚钱
下载
分享
Share
导读
Guide
相册
Album
记录
Doing
搜索
本版
文章
帖子
ToB圈子
用户
免费入驻
产品入驻
解决方案入驻
公司入驻
案例入驻
登录
·
注册
只需一步,快速开始
账号登录
立即注册
找回密码
用户名
Email
自动登录
找回密码
密码
登录
立即注册
首页
找靠谱产品
找解决方案
找靠谱公司
找案例
找对的人
专家智库
悬赏任务
圈子
SAAS
ToB企服应用市场:ToB评测及商务社交产业平台
»
论坛
›
软件与程序人生
›
后端开发
›
.Net
›
JavaSE:多线程详解笔记
JavaSE:多线程详解笔记
科技颠覆者
金牌会员
|
2023-4-4 14:03:27
|
显示全部楼层
|
阅读模式
楼主
主题
906
|
帖子
906
|
积分
2718
JavaSE:多线程学习
01 初识进程
1.1 Process & Thread
1、首先简要介绍
程序
。程序是指令和数据的有序集合,其本身没有任何运行的含义,只是一个静态的概念。
2、
进程
则是执行程序的一次执行过程,是一个动态的概念。是系统资源分配的单位。
3、通常在一个进程中可以包含若干线程。线程是CPU调度和执行的单位。
PS:很多线程是模拟出来的,真正的多线程是指有多个CPU,即多核,如服务器。如果是模拟出来的多线程,即在只有一个CPU的情况下,在同一个时间点,CPU只能执行一条代码。由于切换速度很快,所以会出现同时运行的错觉。
4、线程:
线程就是独立的执行路径;
在程序运行时,哪怕没有手动创建线程,后台也会有多个线程,如主线程,gc线程(垃圾回收);
main()称之为主线程,为系统的入口,用于执行整个程序;
在一个进程中,如果开辟了多个线程,线程的运行由调度器(CPU)安排调度。调度器是与操作系统紧密相关的,先后顺序是不能人为干预的;
对同一份资源进行操作时,会出现资源争夺,需要加入并发控制。
线程会带来额外的花销,如CPU调度时间,并发控制消耗。
每个内存在自己的
工作内存
交互,内存控制不当会出现数据不一致。
02 创建线程
2.1 多线程有三种创建方式
1、Thread class(通过继承Thread类)
2、Runnable接口(实现Runnable接口)
3、Callable接口(实现Callable接口)
2.2 Thread类
1、自定义线程类继承Thread类
2、重写run()方法,编写线程执行体
3、在别的类中创建该线程单位,调用start()方法启动多线程。
2.1-2.2小结
继承Thread类
子类继承Thread类具备多线程能力
启动线程:thread类.start()
但不建议使用,避免OOP单继承的特性
实现Runnable接口
实现接口Runnable具备多线程能力
启动线程:传入目标对象+thread类.start()
推荐使用:避免单继承的局限性,具有很强的灵活性,方便同一个对象被多个线程使用
2.3 Callable接口
1、实现Callable,需要返回值类型
2、重写Call方法,需要抛出异常
3、创建目标对象
4、创建执行服务
ExecutorService ser = Executors.newFixedThreadPool(线程数量)
复制代码
5、提交执行
Future<Boolean> 线程名 = ser.submit(对象名);
复制代码
6、获取返回值
返回值类型 返回值名称 = 线程名.get();
复制代码
7、服务关闭
ser.shutdown();
复制代码
2.4 静态代理
1、真实对象和代理对象要实现同一接口
2、代理对象要代理真实角色
好处:
代理对象可以做很多真实角色做不了的事情
真实对象可以专注于自己的事
2.5 Lambda表达式
1、为什么要使用Lambda表达式
避免匿名内部类定义过多
可以使得代码更加简洁
去掉大部分没有意义的代码,只留下最核心的内部逻辑
2、Lambda表达式的核心是采取函数式编程思想。因此,理解Function Interface(函数式接口)是学习Lambda表达式的关键。
3、函数式接口的定义:
任何一个接口,如果它只包含一个抽象方法,那它就是一个函数式接口。
public interface Runnable{
public abstract void run();
}
复制代码
对于函数式接口,可以采取Lambda表达式的方法创建相关对象。
Lambda表达式使用注意:
只有无参Lambda表达式可以写成如下模式:
new Thread(()->{语句});
复制代码
Lambda表达式只能在有一行代码的情况下才能简化成一行,如果有多行需要用代码块包裹
对象名 = 参数->{语句1;语句;}
复制代码
使用lambda表达式一定要注意是针对函数式接口,即接口中只有一个抽象方法
如果lambda表达式中有多个参数,需要注意格式的统一,即如果有类型就必须都有类型,如果没有就全都去掉
03 线程状态
3.1 线程状态简介
!
3.1.1 线程停止
1、建议线程正常停止,限制次数,不要使用死循环
2、建议使用标志位,即外部调整标志位停止线程运行
3、不要使用stop或destroy等过时或者JDK不推荐的方法
3.1.2 线程休眠
sleep(时间)是指当前线程的阻塞秒数;
sleep存在异常InterruptedException;
sleep时间到达后线程进入就绪状态
sleep可以模拟网络延时和倒计时等(巧妙运用sleep可以发现程序中并发多线程可能存在的问题)
每一个对象都有一个锁,延时不会释放锁
3.1.3 线程礼让
线程礼让是指让正在运行的线程暂停,但不阻塞
保存线程运行断点,回到就绪状态
让CPU重新调度,不一定能礼让成功。
3.1.4 线程合并
Join方法合并线程,待此线程执行结束后,再执行其他线程,会导致其他线程阻塞
可以想象成插队
3.2 线程状态
3.2.1 线程状态观测
线程可以处于以下状态之一:
NEW
尚未启动的线程处于此状态。
RUNNABLE
在Java虚拟机中执行的线程处于此状态。
BLOCKED
被阻塞等待监视器锁定的线程处于此状态。
WAITING
正在等待另一个线程执行特定动作的线程处于此状态。
TIMED_WAITING
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
TERMINATED
已退出的线程处于此状态。
3.2.2 线程优先级
Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定哪个线程优先执行
线程优先级用数字来表示,范围从1-10
Thread.MIN_PRIORITY = 1;
复制代码
Thread.MAX_PRIORITY = 10;
复制代码
Thread.NORM_PRIORITY = 5;
复制代码
使用以下方法来改变或者获取优先级
getPriority() setPriority(inx XXX)
复制代码
备注:1、优先级的设定建议在start()之前
2、优先级低只是意味着获得调度的概率低,并不是优先级低一定就会被后调用。归根结底还是要看CPU的管理。
3.2.3 守护线程和用户线程
线程分为用户线程和守护线程
JVM虚拟机必须确保用户线程执行完毕
但是虚拟机不必等待守护线程执行结束
如:后台监控内存,后台记录操作日志,垃圾回收(gc线程)等
3.3 线程同步
3.3.1 线程同步简介
1、并发:同一个对象被多个线程同时操作
2、处理多线程时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候就需要线程同步。线程同步实际上是一种等待机制,多个需要访问此对象的线程进入这个对象的
等待池
形成队列,等待前面线程使用完毕,再调用下一个线程。
3、队列和锁:
由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带来了访问冲突的问题,为了保证数据在方法中被访问的正确性,在访问时加入锁机制
synchronized
。当一个线程获得对象的排它锁时,就会独占相关资源,其他线程必须等待该线程运行完毕释放锁。但由此会带来一些问题:
一个线程持有锁会导致其他所有需要此锁的线程挂起。
在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时,从而引起性能降低。
如果一个优先级高的线程等待一个优先级低的线程释放锁,就会引起优先级倒置,引发性能问题
3.3.2 三大不安全案例
3.3.2.1 系统购票
(详见代码)
3.3.2.2 银行取钱
(详见代码)
3.3.2.3 线程安全性
(详见代码)
3.3.3 同步方法和同步块
由前面可知,Java可以利用private关键字来保证数据对象只能被方法,因此仿照这个可以针对多线程并发提出一套机制,这套机制就是synchronized关键字。它包括两种用法:synchronized方法和synchronized块。
3.3.3.1 同步方法
同步方法
public synchronized void method(int args){}
复制代码
synchronized方法控制对”对象“的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法的对象的锁才能执行,否则线程会阻塞。方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。
需要注意:若将一个大的方法申明为synchronized将会严重影响程序效率!
此外,synchronized方法针对的是方法中的this类属性。
方法里面需要修改的内容才需要锁;锁的数量太多会影响系统运行效率。
3.3.3.2 同步块
同步块
synchronized (obj){}
复制代码
obj称之为同步监视器
Obj可以是任何对象,但是推荐使用共享资源作为同步监视器
同步方法中无需指定同步监视器,因为同步方法的同步监视器就是this,就是这个对象本身,或者是class。
同步监视器的执行过程:
第一个线程访问,锁定同步监视器,执行其中代码。
第二个线程访问,发现同步监视器被锁定,无法访问。
第一个线程访问完毕,解锁同步监视器。
第二个线程访问,发现同步监视器没有锁,然后锁定并访问。
3.3.4 初识JUC
(详见代码)
3.4 死锁和Lock锁
死锁
Lock锁
从JDK5.0开始,Java提供了更加强大的线程同步机制——通过显示定义同步锁对象来实现同步。
同步锁使用Lock对象充当java.until.concurrent.Lock接口,是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁。线程开始访问共享资源之前应当先获得Lock对象
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁、释放锁。
class A{
private final ReentrantLock lock = new ReentrantLock();
public void m(){
lock.lock();
try{
//保证线程安全的代码;
}finally{
lock.unlock();
//如果同步代码有异常,要将unlock()写入finally语句块
}
}
}
复制代码
synchronized与Lock的对比
Lock是显示锁(手动开启和关闭锁,别忘记关闭锁)synchronized是隐式锁,出了作用域自动释放(代码块或是方法)
Lock只有代码块锁,synchronized有代码块和方法锁
使用Lock锁,JVM将花费更少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
优先使用顺序:
Lock>同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)
04 线程协作
4.1 线程通信问题
应用场景:生产者和消费者问题
假设仓库中存有一定量的物品,生产者将生产出的产品放入仓库,消费者消费仓库中的产品;
如果仓库中没有物品,生产者将生产出的产品放入仓库,消费者停止消费等待仓库中有物品;
如果仓库中物品已满,生产者停止生产并等待,消费者消费仓库中的产品。
这是一个线程同步问题,生产者和消费者共享同一个资源,并且生产者和消费者互为依赖,互为条件。
对于生产者,没有生产产品前,要通知消费者等待,而生产了产品之后,又需要马上通知消费者消费。
对于消费者,在消费之后,要通知生产者已经结束消费,需要生产新的产品以供消费。
在生产者消费者问题中,仅有synchronized是不够的
synchronized可阻止并发更新同一个共享资源,实现了同步
synchronized不能用来实现不同线程之间的信息传递(通信)
Java提供了几个方法解决线程之间的通信问题
方法名作用wait()表示线程会一直等待,直到其他线程通知。与sleep不同,会释放锁wait(long timeout)指定等待的毫秒数notify()唤醒一个处于等待状态的线程notifyAll()唤醒同一个对象上所有调用wait()方法的线程,优先级别高的优先被调用
注意:这些均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常IllegalMonitorStateException
4.2 解决办法一:管程法
并发协作模型”生产者/消费者模式“--->管程法
生产者:负责生产数据的模块(可能是方法,对象,线程,进程);
消费者:负责处理数据的模块(可能是方法,对象,线程,进程);
缓冲区:消费者不能直接使用生产者的数据,他们之间有个“缓冲区”;
生产者将生产好的数据放入缓冲区,消费者从缓冲区拿出数据。
4.3 解决办法二:信号灯法
信号灯法其实就是利用一个外部标志位,结合wait()方法控制整个线程的运行
4.4 解决办法三:线程池
JDK5.0开始提供了线程相关的
API:ExcutorService
和
Executor
ExcutorService
:真正的线程池接口。常见子类ThreadPoolExcutor
void execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable
Futuresubmit(Callabletask):执行任务,有返回值,一般用来执行Callable
void shutdown():关闭连接池
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
05 总结
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
本帖子中包含更多资源
您需要
登录
才可以下载或查看,没有账号?
立即注册
x
回复
使用道具
举报
0 个回复
正序浏览
返回列表
快速回复
高级模式
B
Color
Image
Link
Quote
Code
Smilies
您需要登录后才可以回帖
登录
or
立即注册
本版积分规则
发表回复
回帖并转播
发新帖
回复
科技颠覆者
金牌会员
这个人很懒什么都没写!
楼主热帖
XAML 设计器已意外退出。(退出代码: e0 ...
OpenCV提取十字标中心点的几种思路 ...
我分析30w条数据后发现,西安新房公摊 ...
Python itertools 库的使用记录
Windows | RDPWrap 远程桌面登录加强工 ...
计算机网络学习—计算机网络概述 ...
码上加速,低代码解锁高效交付案例 ...
SQLI-LABS(Less-7)
WPF 使用 MAUI 的自绘制逻辑
Cesium 案例(二)Web MapTile Service ...
标签云
存储
挺好的
服务器
快速回复
返回顶部
返回列表