线程、线程组、线程池、锁、事务、分布式

发布于:2024-04-26 ⋅ 阅读:(21) ⋅ 点赞:(0)

1.线程 Thread类 ,可以继承他,复写run方法,然后new一个对象,调用start方法启动。

2.runnable接口,他单独把run方法定义出来了,可以自己实现一个runnable接口,然后new一个runnable对象给到thread的构造方法中,调用thread的start方法

线程中断 - interrupt   再run方法中,你随时可以调用Thread.currentThread().isInterrupted() 方法判断当前线程是否被中断了,然后判断是否继续执行还是终止执行,如果是调用了业务方法,你可以抛出InterruptException异常 让上层方法来判断继续还是终止。

线程停止:stop   他是抛出error,不能进行捕获,是jvm层处理的,他会再线程执行下一个指令的时候终止掉线程操作

中断、停止都会调用 finally 代码块、 closeable资源的close方法(你得定义再try的小括号里面)

中断较为温和,stop较为极端

线程thread参数:name、runnable、threadgroup、tasksize,前面两个可以单个给,前面三个可以两两给或者都给,也可以四个都给。

name名字,不给的话他有个线程数量累加到名字后缀里面,前缀是固定的一个字符串。

runnable:线程要执行的具体任务,thread的run方法默认是调用runnable的run方法

threadgroup:线程组,给线程分配到那个组里面,不给默认是给到调用者当前线程所在组(优先获取System.getSecurityManager(),再获取当前线程组)目的可能是为了后期维护,目前他那个也是拿当前线程组

线程组:相当于是一颗树结构,只会有一个根,因为所有公共构造方法最后都会默认加入到一个parent里面,你又不能new一个没上级的group,除非用反射,调用其他私有构造方法。

线程组记录了上一个线程组、当前线程数、当前线程数组、当前线程组数、当前线程组 数组

注:组设置了守护线程,后续加的线程默认都是守护线程(也可以手动再改),主线程都跑完了,只有守护线程了,jvm会退出,线程不能再运行时候改变性质。

线程池:生产者、消费者模式,有初始大小、最大容量、任务队列、存活时间、时间单位、线程初始工程、拒绝策略

执行方法:

1.execte :没有返回结果的

2.submit:有返回结果(Future)

3.往队列里面手动加:如果没有核心线程再跑,不会执行的

注:如果入参是实现了callable接口,他会返回callable的结果,如果不是就会返回指定的value,不指定,get方法会返回空(这里的结果是要future的get方法去获取)future的get方法是要等待的,等子线程执行完。

线程控制:execute\submit会开启核心线程立即执行,如果核心数量满了,他会存队列里面,如队列满了,他会开启临时线程跑,如超过最大线程数量,会执行拒绝策略,默认是抛出异常让调用者处理,也可以使用其他策略,比如让调用者执行、丢弃当前任务、丢弃队列最前面的任务,或者自定义策略(RejectedExecutionHandler接口)。

他一次并发数量最多是 最大核心线程数量+队列数量 ,超过了就触发拒绝策略。

当队列里面数量没了,临时线程执行完当前任务就会停止,核心线程就会等待(getTask)

线程池关闭  shutdown 会处理完队列里面的任务,  shutdownNow 会中断当前正则执行的线程,然后清空队列,返回当时队列没处理完任务集合

锁:防止资源并发问题,比如:我要改某个资源,别人也要改这个资源,就要用到锁

大类:

乐观锁:修改前拿取一个版本号,修改后对比版本号,一致就修改,不一致就失败,要保证对比和修改的原子性,适合用在并发不是很高的场景

悲观锁:占用一个资源锁住,不让别人修改、读取,然后我处理完了,修改好了,再让别人读取、修改,适合用在并发高的场景。然后为了资源不占用太大可以设置锁等待超时时间。

自旋锁:乐观锁情况:修改失败了再重新操作,悲观锁:锁超时了我再去获取锁;不建议使用,可能导致死锁,或者严格控制重试次数

synchronized关键字 是以某个对象作为锁,非公平的,独占,可重入的,不能自己控制,jvm处理

AQS: java提供抽象的队列锁类,继承了独占锁(记录了当前获取到锁的线程)

队列锁(继承了独占锁,使用了等待队列,双链结构的队列)、

公平锁(lock队列按照时间顺序排序,目的是unlock时让先lock的拿到锁,晚lock的继续等)、

读写锁(分为读锁和写锁,读和读之间不互斥,读和写互斥,也叫共享锁和互斥锁;互斥也就是读锁再lock了,写锁不能lock;写锁lock了, 读锁不能lock;不互斥,也就是读锁lock了,别人也可以lock读锁,同时读;是可重入的(同一种锁情况可以,不同锁的话会死锁,自己和自己纠缠死锁了),重入了多少次就要unlock多少次,可以通过锁对象获取次数)

UNSAFE保证方法的原子性,比较、赋值同时操作等方法,LockSupport有些方法底层也是UNSAFE提供的。

LockSupport  阻塞当前线程  park (一直等下去,直到unpark)、parkUntil(或者到某个时间 - 绝对) 、parkNanos(或者等多少时间 - 相对)

参数:blocker 阻塞者,可以通过LockSupport.getBlocker 获取(需要线程参数)

时间参数:long类型的,通过时间对象getTime获取,他是指距离某个时间点(自行百度吧)的毫秒数。

LockSupport  放行方法unpark 要给一个线程参数,放行谁。(可以放到park前面,这样线程park就可以直接通过,不用等待(必须再start后,否则不行),方便线程调度)

如果有多个地方park,必须开始以及再每个间隔内给一个unpart,如果间隔内给了两个unpart,他会卡住,因为重复给unpark,他只记录一次的

事务:保证一堆读、写操作要么全部成功,要么全部失败,成功了就要保证读的数据正常、写入的数据准确以及不能丢失。

原子性 :对一批操作是独立的,执行了要么成功要么失败,比如:读两条数据,写、改三条数据,不可以我读成功了,写失败了;或者我读失败了,给我写成功了;或者我只写入了一条数据等等。

一致性:我期间读取的数据是准确的,写入的正确的,然后提交成功。比如事务期间读了一条数据,他是4,然后期间别的事务改成了3、5、4,这时候要失败,哪怕他最后的值是4也要失败,所以要加数据版本进行比较,以及期间数据要一次性取出来再处理,如果分开取,期间别的事务修改了,你数据版本比较是检查不出来的(比如 你获取两条数据,A和B,你获取A的时候,B的版本是2,然后别人改了B,版本是3了,你再获取B,这时候你的B版本获取的是3,不是你最开始获取A时候的B了,你提交事务的时候检验B的版本是3是检验不出来的)。

隔离性:事务之间的数据隔离,保证一致性,有读未提交、读已提交、可重复读、事务串行

读未提交:会发生脏读:数据库原本是2,A事务改成了3,没提交,然后B事务读A,结果是3,然后A事务回退,B事务基于A为3的基础上进行其他逻辑处理(实际是2)。(读了错误的数据)

读已提交:会发生幻读:数据库原本是2,A事务读取为2;B事务修改成3,提交了;A事务再去读取成了3。(不能重复的读,每次读都是对的数据,但是数据变动了)

可重复读(oracle没有这个等级,用临时表实现):A事务开始读的时候是2,B事务改成了3,提交了,A事务再去读,依旧是2.提交的时候要进行数据版本比对,否则会有逻辑问题。

事务串行执行:事务一个一个执行,不会发生任何问题,但是执行效率低。

持久性  提交之后的数据要保存到硬盘,系统关了,下次启动还能读写。

分布式:多个系统,事务存在依赖关系,但是系统间网络不稳定。

问题:我当前事务执行到一半,调用其他系统,要等反馈才能决定接下来的逻辑,以及事务是否提交。

解决办法:事务最终一致性:拆分事务:记录当前事务状态到一张表,相关数据资源先占用,然后提交事务;定时去别的系统查结果,然后根据结果进行处理(成功就继续后续操作,失败就释放之前占用的数据资源)。

注:占用数据资源、释放数据资源要根据实际需求来判定,

比如:买东西,别人付款了,我就把库存先扣掉,记录到一个数据到等待支付结果确定的表里面,提交。别的事务可以基于扣除后的库存继续扣除啥的。然后定时器查询结果,如果支付成功,继续后续逻辑:通知物流系统、订单系统等,改下表状态,如果支付失败,就退回库存,改表状态。

分布式事务,系统间消息通讯是很重要的,不能丢失消息没处理,比如状态表的数据是绝对不能丢失的,否则库存资源一直占用,后续逻辑一直没处理。

分布式并发的情况下,可能导致系统处理不过来,既要处理用户订单支付,又要定时查表做后续处理。就需要定时任务处理服务,然后两个服务间需要mq做中间件,保证消息准确。

A服务发送事务状态数据到mq,然后mq路由消息到指定的B服务做处理(生产者消费者模式)生产者就是A服务,消费者是B服务,但是一个系统有很多业务流,所以会有很多生产者和很多消费者;这就有了各种模式。