Java线程
2023-12-26 19:01:39 举报
AI智能生成
登录查看完整内容
相似推荐
查看更多
多线程原理图
Java多线程6大状态
java多线程
线程池处理流程
JAVA
Java 基础
Java 集合
Java多线程
Java学习
Java多线程
Java线程
作者其他创作
大纲/内容
为了提升性能,处理器和编译器尝尝会对指令进行重排序
保证单线程内执行结果不会改变
as-if-serial
保证正确同步的多线程执行结果不会改变
happens-before
两个原则
不能改变程序运行的结果
存在数据依赖的不允许重排序
需要满足的条件
虽然不会影响单线程的执行结果,但会破坏多线程的执行语义
重排序
悲观锁、可用于类,方法,代码块,会阻塞线程
使用对象头中的mark down实现加锁
synchronized
提供线程间共享变量,禁止指令重排序,只保证可见性不保证原子性,不会阻塞线程
volatile
只能给代码块加锁,必须手动加锁、解锁
Lock实现类,底层调用的是unsafe的park实现加锁
ReentrantLock
Lock
基于冲突解决的乐观锁(自旋)
CAS
synchronizedvolatileLockCAS
乐观锁的一种实现
包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和 A 的值是一样的,那么就将内存里面的值更新成 B。CAS是通过无限循环来获取数据的,若果在第一轮循环中,a 线程获取地址里面的值被b 线程修改了,那么 a 线程需要自旋,到下次循环才有可能机会执行。
一个线程 one 从内存位置 V 中取出 A,这时候另一个线程 two 也从内存中取出 A,并且 two 进行了一些操作变成了 B,然后 two 又将 V 位置的数据变成A,这时候线程 one 进行 CAS 操作发现内存中仍然是 A,然后 one 操作成功。
jdk1.5之后再Atomic包中增加了AtomicMarkableReference(引入一个boolean值来判断中间是否变动过),AtomicStampedReference(通过引入一个int累计来判断中间是否变动过)来解决ABA问题
解决方法
ABA问题
资源竞争大时,自旋概率会变大,浪费CPU资源
循环时间开发大
会出现问题
CAS(compare and swap)
执行策略调用,调度,执行和控制异步任务的框架
执行线程任务
提供了更多的方法我们能获得任务执行的状态并且可以获取任务的返回值
ExecutorService(继承Executor)
Excutor
线程池创建框架
Excutors
ExcutorExcutors
表示异步任务,是一个可能还没有完成的异步任务结构
Callable产生结果,Future获取结果
Future
一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。
是一个自旋锁,能确保无饥饿性,提供先来先服务的公平性。
线程池任务队列超过 maxinumPoolSize 之后的拒绝策略
都采用Thread.start()启动线程
相同点
不会返回结果,且不会抛出返回结构异常
Runnable
call方法有返回值,配合Future\\Future Task可以获取返回结果
call方法允许抛出异常,可以捕获异常信息
Callable
不同点
Runnable/Callable
显示监视器(Lock)
每个监视器与一个对象引用关联
每个对象都有一把锁
隐示监视器(Synchroniezd)
监视器
同步方法
同步代码块
重入锁
特殊变量与volatile
锁
实现同步的方法
死磕ThreadLocal
不remove是否真的就有问题?
使用完threadlocal后,需要手动调用remove()方法
线程池中:execute()只能执行 Runnable 类型的任务。submit()可以执行 Runnable 和 Callable 类型的任务。
long\\dubbo类型不具有原子性,多个线程并发下数据会有线程安全性问题((64位字段会被拆成两个32位字段处理)可能读到的值,不是任何一个线程所赋的值)
注意事项
threadLocal/threadLocalMap
充分利用多核CPU的性能,使CPU的计算发挥到极致
优点
内存占用
上下文切换
资源竞争
缺点
优/缺点
使用Atomic原子类、Sychronized、Lock解决
线程切换导致的原子性问题
使用Synchronized\\Lock\\Volatile解决
缓存导致的可见性问题
Happen-Before原则
编译器优化带来的有序性问题
安全问题及解决方案
1.将一个线程设置成守护进程(setDaemon())必须在线程启动之前;2.守护线程产生的也是守护线程;3.不是所有线程都可以成为守护线程,比如读写操作和逻辑运算;4.守护线程中不能依靠finally块来确保关闭或释放资源。因为没有非守护线程运行,程序会直接结束。
守护线程
一个资源一个线程获取
互斥性
请求资源阻塞时,不释放获取的资源
解决:一次性获取所有锁
请求与保持
线程获取的资源,不可被其他线程强行剥夺
解决:主动释放
不可剥夺
多个线程行程循环
解决:顺序获取资源,反序释放资源锁
循环等待
死锁四个条件
继承thread类
实现runnable接口
实现callable接口
线程池
创建线程的方式
线程具体执行的内容
run()
启动一个线程,使线程进入一个准备就绪状态
start()
run()\\start()方法
(1) wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;(2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理 InterruptedException 异常;(3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;(4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;(5)yield 方法让出了对 cpu 的占用权利;
线程五个基本状态
按照特定机制给多线程分配CPU使用权利线程调度不受虚拟机控制,由系统控制
分时调度:平分每个线程CPU占用时间
抢占调度(jvm使用方式):优先级高的线程先执行
调度策略
1.是个操作系统服务;2.将cpu可用的时间片分配给各个Runnabel状态的线程;
调度器
将可用的CPU时间分配给Runnable状态的线程
线程优先级
线程等待时间
分配方式
时间分片
线程调度
线程中断仅仅是线程的中断状态,线程不会立即停止,需要用户去监视线程的状态并做处理,支持线程中断的方法(也就是线程中断后会抛出interruptedException 的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。
LockSupport.park()\\wait()\\sleep均响应中断
用于中断线程,调用该方法的线程状态将被设置为“中断”
interrupt
静态方法,查看状态是True还是False会消耗中断状态
interrupted
查看当前中断信号
isInterrupted
interruptinterruptedisInterrupted
线程中断interrupt()
会释放锁
wait()的使用必须要被唤醒才能重新执行
因为有可能被唤醒之后“锁”等条件不满足需要继续等待
应该在循环中调用
线程进入锁对象的等待池,等待被唤醒
wait()使用notify或者notifyAlll或者指定睡眠时间来唤醒当前等待池中的线程。
wiat()必须放在synchronized block中,否则会在program runtime时扔出”java.lang.IllegalMonitorStateException“异常。
wait()
由JVM确定唤醒哪个线程,且与优先级无关
notify()
唤醒所有等待状态的线程竞争
会唤醒等待池中所有线程参与竞争,竞争成功就继续执行,失败的在锁池继续等待锁释放参与竞争
notifyAll()
只会给相同优先级或者更高优先级线程运行机会
yield()
不会释放锁
调用sleep()让出CPU时,不会考虑线程优先级,会给低优先级线程运行机会
sleep()
基础方法
syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()
ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()
通过中断和共享变量
使用管道进行线程间通信
线程间直接数据交换
LockSupport用法及原理
LockSupport
通信协作常见方式
共享的进程系统资源任意时刻最多只允许一个线程使用
互斥
不需要切换内核态,只在用户态下完成
原子操作
临界区
方法
用户模式
利用系统内核对象的单一性进行同步;需要切换内核 态和用户态
事件
信号量
互斥量
内核模式
同步方法(Synchronized)
同步代码块(Synchronized、Lock)
volatile修饰变量
同步
同步与互斥
总的有1-10个级别,数值越大优先级越高
java对线程的调度会委托给系统,系统不同而不同,所以不建议使用线程优先级
进程的内存镜像,把程序的运行状态通过调试器保存在dump文件中
以通过命令 kill -3 PID (Java 进程的进程 ID)来获取 Java应用的 dump 文件。
Linux
按下 Ctrl + Break 来获取。这样 JVM 就会将线程的 dump 文件打印到标准输出或错误文件中,它可能打印在控制台或者日志文件中,具体位置依赖应用的配置。
Windows
获取方式
线程dump文件
基本概念
保证修改的值会被立即更新到主存中,其他地方读取也是从主存中读取
保证内存可见性(缓存一致性协议),禁止重排序(内存屏障)
不保证原子性,只保证可见性和有序性
1.6之前,重量级锁,效率低(因为监视器依赖底层的Mutex Lock实现,java的线程是映射到底层的原生线程上,要唤醒、挂起线程都需要操作系统帮忙,而操作系统切换线程需要重用户态转换到内核态(这一步比较耗时))
1.6之后,使用\"自旋锁\
synchronized加载静态方法和synchronized(Class)都是给类加锁
synchronized记在实例方法上,是给对象加锁
synchronized关键字是不能继承的
不要使用synchronized(String a),因为JVM中有字符串常量池
让获取锁的线程不阻塞,而是在synchronized边界做盲目循环
自旋
概述
监视器功能;主要为monitor获取锁,monitorexit释放锁;其中有两个monitorexit;后面一个monitorexit为保证异常退出也会释放锁
可以使用命令>>javap -c 名字 查看
底层原理
底层维护计数器
可重入锁原理
锁对象的对象头有一个threadid字段,第一次进来为空,jvm让其持有偏向锁,并将threadid设置为当前线程ID;再次有线程进入时,如果线程ID一致则直接使用,如果不一致则升级锁为轻量锁;通过自旋获取锁,自旋一定次数之后则升级轻量级锁为重量级锁
无状态锁
偏向锁
轻量级锁
重量级锁
四种状态
随着资源竞争的激烈,状态会依次升级,锁只能升级,不会降级
锁升级原理
并发关键字
因争夺资源而导致的相互等待、无外力作用将无法推进下去
死锁
任务为阻塞,但由于某些条件未满足,导致一直重试、失败、重试。。。
活锁
一个或多个线程因为某种原因而一直无法获取资源,导致一直无法执行
高优先级一直吞噬低优先级CPU时间
一直阻塞在等待同步块的状态
线程本身等待一个处于永久等待的对象(比如调用对象的wait()方法-)
原因
饥饿
死锁活锁饥饿
1. 在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功;2. 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。
实现原理
获取锁的顺序符合请求上的先后性
公平锁
非公平锁
支持公平锁和非公平锁
读写分离,读锁共享,写锁分离
支持公平与非公平的锁获取方式,非公平式的吞吐量高于公平式的吞吐量
公平选择
读锁、写锁都支持重进入
重进入
遵循获取写死在获取读锁的次序,写锁能够降级为读锁
锁降级
三个特征
ReentrantReadWriteLock(读写锁)
ReentrantLock(可重入锁)
表示一个异步计算任务
可以传入一个Cllable异步运算,获取异步运算结果
是Runable接口的实现类,也可以放入线程中执行
FutureTask
原理:CAS(自旋)+volatile(内存可见):仅有一个线程能成功,而未成功的线程可以像自旋锁一样,继续尝试,一直等到执行成功。
AtomicBoolean
AtomicInteger
AtomicLong
AtomicReference
原子类
AtomicIntegerArray
AtomicLongArray
AtomicReferenceArray
原子数组
AtomicLongFieldUpdater
AtomicIntegerFieldUpdater
AtomicReferenceFieldUpdater
原子属性更新器
原子操作(不可被中断的一个或一系列操作)
强调一个线程等多个线程完成某件事情
CountDownLatch(倒计时器)
多个线程互等,等大家都完成,再携手共进
CyclicBarrier(循环栅栏)
信号量,限制某块代码的并发量
Semaphore(信号量)
用于两个线程间交换数据。它提供了一个交换的同步点,在这个同步点两个线程能够交换数据
Exchanger(线程间数据交换)
指某个线程从其他队列里窃取任务来执行
工作窃取
fork join
并发工具
继续添加任务到队列中
无界队列
如果没有到达最大设定线程数,则创建线程;如果到达最大线程数,则执行拒绝策略
有界队列
队列满了
线程队列
正常状态,接收新任务,处理等待队列中的任务
Running
不接受新任务,会继续处理任务队列中的任务
Shutdown
终止当前执行任务,不接受新任务,不执行等待队列中的任务
Stop
当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理;可以通过重载terminated()函数来实现。
Tidying
terminated()方法执行之后的状态
Terminated
线程及线程池状态
线程池存在的状态
newSingleThreadExecutor(单线程,异常等原因结束之后会创建新线程代替)
newFixedThreadPool(线程池大小固定,有线程挂掉之后会有新线程来代替)
newCachedThreadPool(有线程空闲60秒以上会回收,需要的时候再创建,最大数由操作系统决定)
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。
newScheduledThreadPool
Executors
核心线程数(最少线程数)
corePoolSize
maximumPoolSize最大线程数
新任务来先判断是否使用完了所有核心线程如果使用完了就把任务放在队列中
等待队列
ArrayBlockingQueue
支持优先级的无界阻塞队列,使用较少
PriorityBlockingQueue
Integer.MAX_VALUE
吞吐量高于ArrayBlockingQueue
Executors.newFixedThreadPool()使用该队列
LinkedBlockingQueue
不储存元素(无容量)的阻塞队列;每个put操作必须等待一个take操作
吞吐量高于LinkedBlockingQueue
Executors.newCachedThreadPool使用该队列
SynchronousQueue
workQueue
线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime 才会被回收销毁;
空闲线程存活时间
keepAliveTime
keepAliveTime 参数的时间单位
unit
为线程池提供创建新线程的线程工厂
threadFactory
handler
构造参数
ThreaPoolExecutor
创建方式
可以执行Runnable、Callable类型任务
可以返回持有异步执行结果的Future对象
submit()
只能执行Runnable()类型任务
execute()
submit()/execute()
抛出 RejectedExecutionException 来拒绝新任务的处理。
中止策略-AbortPolicy(默认使用方式)
由调用线程处理该任务
调用者运行-CallerRunsPolicy
不处理新任务,直接丢弃掉
抛弃策略-DiscardPolicy
此策略将丢弃最早的未处理的任务请求。
抛弃旧任务策略-DiscardOldestPolicy
饱和策略
计算密集型:尽可能少的线程执行,例如cpu+1
IO密集型:尽可能多的线程执行,例如CPU*2
混合型:可以考虑是否可以拆分为IO密集型和计算密集型
1.任务性质:计算密集型/IO密集型/混合型;
2.任务优先级:高、中、低
3.任务执行时间:长、中、短
如果依赖其他耗时的资源,则应设置尽可能多的线程
4.任务的依赖性:是否依赖别的资源(数据库,网络等资源)
分析角度
1.按照高中低划分给不同的线程池执行
2使用优先级队列(需要考虑低优先级可能一直得不到执行)
概要
最佳线程数=[(线程执行时间+线程等待时间)/线程执行时间] * CPU数
如何配置参数
中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其它线程进行了中断操作,但是通过中断并不能直接终止另一个线程,而是需要中断的线程自己处理。
线程的中断不会立马影响线程的状态,线程中断前默认标志位为false,中断后标志位被修改true,标志位被修改后,子线程并没有马上执行中断,而是在主线程继续执行一段时间后才执行中断
测试当前线程是否被中断,该方法可以消除线程的中断状态,如果连续调用该方法,第二次的调用会返回false。
interrupted()
测试线程是否已经中断,中断的线程返回true,中断的状态不受该方法的影响
isInterrupted()
中断线程,将中断状态设置为true
interrupt()
相关方法
总结:线程中断不会使得线程立马退出,而是会给线程发送一个通知,告诉目标线程你需要退出了,具体的退出操作是由目标线程来执行
线程中断
简单的在需要同步的方法上加上关键字 synchronized
Vector
Hashtable
Collections.synchronizedSet,synchronizedList 等方法返回的容器
同步容器
采用细粒度的分段锁和内部分区等技术
另外:使用了一种不同的迭代方式。在这种迭代方式中,当iterator 被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时 new 新的数据从而不影响原有的数据,iterator 完成后再将头指针替换为新的数据 ,这样 iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。
ConcurrentHashMap
写入将导致创建整个底层数组的副本,而源数组将保留在原地,使得复制的数组在被修改时,读取操作可以安全地执行。
CopyOnWriteArrayList
程可以访问自己内部 ThreadLocalMap 对象内的 value。通过这种方式,避免资源在多线程间共享。
内存泄漏问题
在调用 set() 、get() 、remove() 方法的时候,会清理掉 key 为 null 的记录。使用完 ThreadLocal 方法后 最好手动调用remove() 方法
内存泄漏解决方法
ThreadLocal
在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。
PryorityBlockingQueue
DelayQueue
LinkedTransferQueue
LinkedBlockingDeque
BlockingQueue(主要作为线程同步的工具)
并发容器(效率更高)
同步容器并发容器
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
核心思想
Unsafe(提供CAS操作)
LockSupport(park/unpark)源码分析
LockSupport(提供park/unpark操作)
实现工具
公平锁:线程在队列中排队顺序,先到先得
非公平锁:无视队列所有线程去抢
独占
semaphore
countDownLatch
SyclicBarrier
readWriteLock
多个线程同时进行
共享
资源共享方式
Java并发之AQS详解
AQS(AbstractQueueSychronizer)(构建锁和同步器的框架)
Java线程
0 条评论
回复 删除
下一页