Java多线程、并发编程指示点学习笔记总结
2022-10-27 17:59:35 0 举报
AI智能生成
Java多线程、并发编程指示点学习笔记总结
作者其他创作
大纲/内容
并发编程
相关理论
重排序
为了提高程序执行性能,编译器(编译期)和 CPU(运行期)会对指令进行重排序
三大特性
原子性
线程对共享变量的操作是原子性操作,就是说该操作或多个操作,要么都成功要么都失败
gt;对于复合操作而言,synchronized lt;/fontgt;关键字可以保证原子性,而 gt;volatilelt;/fontgt; 关键字不能
可见性
线程对共享变量的修改对其它线程立即可见
gt;synchronizedlt;/fontgt; 和 gt;volatilelt;/fontgt; 关键字都能保证可见性
有序性
程序代码执行的结果不受JVM指令重排序的影响
由于 gt;volatilelt;/fontgt; 关键字可以禁止指令重排序,因此能保证有序性。
lt;bgt;gt;Lock lt;/fontgt;锁机制可以同时保证以上三个特性lt;/bgt;
并发编程容易出问题的三方面
1.安全性问题:存在竞态条件。所谓竞态条件,指的是程序的执行结果依赖线程执行的顺序。
活跃性问题:指的是某个操作无法执行下去。我们常见的“死锁”就是一种典型的活跃性问题,当然除了死锁外,还有两种情况,分别是“活锁”和“饥饿”。
活锁:有时线程虽然没有发生阻塞,但仍然会存在执行不下去的情况,这就是所谓的“活锁”。可以类比现实世界里的例子,路人甲从左手边出门,路人乙从右手边进门,两人为了不相撞,互相谦让,路人甲让路走右手边,路人乙也让路走左手边,结果是两人又相撞了。这种情况,基本上谦让几次就解决了,因为人会交流啊。可是如果这种情况发生在编程世界了,就有可能会一直没完没了地“谦让”下去,成为没有发生阻塞但依然执行不下去的“活锁”
“饥饿”指的是线程因无法访问所需资源而无法执行下去的情况。“不患寡,而患不均”,如果线程优先级“不均”,在 CPU 繁忙的情况下,优先级低的线程得到执行的机会很小,就可能发生线程“饥饿”;持有锁的线程,如果执行的时间过长,也可能导致“饥饿”问题。
性能问题。
解决手段
既然使用锁会带来性能问题,那最好的方案自然就是使用无锁的算法和数据结构了。
减少锁持有的时间。互斥锁本质上是将并行的程序串行化,所以要增加并行度,一定要减少持有锁的时间。
JMM
gt;Java Memory Modellt;/fontgt;,即gt; Java lt;/fontgt;内存模型
关键概念
共享内存
即主存,所有线程共享。
(线程)本地内存
也称为“工作内存”。gt;JVMlt;/fontgt; 给每个线程都分配了一块内存区域,该块内存是线程独有的。
从架构上看,类似于计算机硬件架构中 gt;CPUlt;/fontgt; 与内存间的高速缓存
gt;JMMlt;/fontgt; 规定:线程不能直接操作主存,而是只操作属于自己的那部分内存。如果多个线程间需要进行变量共享,必须经过主存。
由于gt; JMM lt;/fontgt;的限制,线程操作变量都要经过以下几个基本步骤:1、从主存中读取变量放入工作内存;2、在工作内存中对变量进行修改操作;3、将操作后的结果同步回主存。
Happens-Before 原则
如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在Happens-Before关系。
和程序猿相关的六条规则
1.程序的顺序性规则
在一个线程中,按照程序顺序,前面的操作 Happens-Before 于后续的任意操作
2.volatile变量规则
对一个 volatile 变量的写操作, Happens-Before 于后续对这个 volatile 变量的读操作。
3.传递性
如果 A Happens-Before B,且 B Happens-Before C,那么 A Happens-Before C。
4.管程中锁的规则
指管程中对一个锁的解锁 Happens-Before 于后续对这个锁的加锁。
分支主题
5.线程start()规则
这条是关于线程启动的。它是指主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作。
6.线程join()规则
这条是关于线程等待的。它是指主线程 A 等待子线程 B 完成(主线程 A 通过调用子线程 B 的 join() 方法实现),当子线程 B 完成后(主线程 A 中 join() 方法返回),主线程能够看到子线程的操作。当然所谓的“看到”,指的是对共享变量的操作。
线程安全问题
衡量标准:如果一段程序在多线程环境下多次执行的结果总是与预期一致,就说是线程安全的,反之则是线程不安全
所谓的线程安全问题,其本质在于线程对共享变量操作的原子性、可见性、有序性不能同时满足,因此解决线程安全问题的关键就在于使其同时满足该三个特性。
gt;Unsafelt;/fontgt; 类
该类提供了在 gt;Javalt;/fontgt; 中能够像 gt;C/C++ lt;/fontgt;语言使用指针直接操作内存的功能,由于直接操作内存可能会发生风险,因此Java官方不建议直接使用该类,也没有提供关于该类的文档。但是在 gt;JDKlt;/fontgt; 源码中为了提高运行效率在很多地方都使用了该类。
包含的功能
内存管理
包括分配内存、释放内存、获取变量内存地址偏移量、通过变量地址偏移量获取和修改变量值等
线程挂起与恢复
gt;park、unparklt;/fontgt; 方法
gt;CASlt;/fontgt; 无锁技术
gt;CAS lt;/fontgt;即 gt;Compare And Swaplt;/fontgt; 的缩写,比较并交换。属于乐观锁的一种实现方式。
通过 gt;JNIlt;/fontgt; 调用 gt;CPU lt;/fontgt;的 gt;CAS lt;/fontgt;指令实现,在硬件层面实现了原子操作。
它是并发包中的锁机制和原子操作类实现的底层基础
实现原理
CAS 有3个操作数,内存值 V,旧的预期值 A,要修改的新值 B 。当且仅当预期值 A 和内存值 V 相同时,将内存值 V 修改为 B,否则什么都不做。若操作成功则返回结果,否则会进行重试直到成功为止。
缺点
只能保证对一个共享变量进行原子操作
如果要多个共享变量的原子操作,需要用到锁机制
解决办法
并发包中引入了针对引用类型的原子操作类(如 AtomicReference、AtomicReferenceArray)。因此我们可以将多个变量放到同一个对象中,然后用引用类型的原子操作类来操作也可以保证原子性。
ABA问题
当一个值从 A 变为 B ,又从 B 变回了 A,这种情况下,CAS 会认为值没有发生过变化,但实际上是有变化的。
解决办法
并发包中的 AtomicStampedReference 类引入了一种基于版本号的机制,来保证对于引用类型的CAS操作的原子性。
高并发时效率降低
由于存在自旋锁(spin lock)机制,会导致CAS的失败次数会增多,从而引起更多线程的重试
解决办法
在高并发情况下,使用Java 8中新增的四个类:DoubleAccumulator、DoubleAdder、LongAccumulator、LongAdder
gt;ThreadLocallt;/fontgt; (线程本地变量)
它为每个线程都提供一个独立的变量副本,各个线程都可以改变自己的变量副本,各个线程间互不影响。
实现的思路:1)在 gt;ThreadLocallt;/fontgt; 类中有一个静态内部类 gt;ThreadLocalMaplt;/fontgt;,用于存储每一个线程的变量副本,键为当前gt; ThreadLocallt;/fontgt; 对象,而值则是对应线程的变量副本;2)每个 gt;ThreadLocallt;/fontgt; 实例都有一个唯一的gt; threadLocalHashCodelt;/fontgt;(这个值将会用于在 gt;ThreadLocalMaplt;/fontgt; 中找到gt; ThreadLocallt;/fontgt; 对应的gt; valuelt;/fontgt; 值)。
工作原理:1)在 gt;Threadlt;/fontgt; 类中维护一个gt; ThreadLocalMaplt;/fontgt; 类型的属性 gt;threadLocalslt;/fontgt;(与线程进行绑定,避免了线程安全问题);2)取值 gt;getlt;/fontgt; 操作:① 首先从 gt;ThreadLocal lt;/fontgt;中取变量值时先获取当前线程,然后获得当前线程的 gt;threadLocalslt;/fontgt; 属性(即与当前线程关联的gt; ThreadLocalMaplt;/fontgt;);② 然后再从 gt;ThreadLocalMaplt;/fontgt; 中根据当前gt; ThreadLocallt;/fontgt; 获取到其中的变量值(如果 gt;ThreadLocalMaplt;/fontgt; 为空,则返回初始化方法 gt;initialValue() lt;/fontgt;的值,默认是gt; nulllt;/fontgt;,可通过重写该方法自定义初始值);gt;微软雅黑gt;3)lt;/fontgt;setlt;/fontgt; 操作:找到当前线程关联的ThreadLocalMap,将当前 ThreadLocal 对象为 key,以变量值为value存入ThreadLocalMap中(如果ThreadMap为null,则创建一个新的ThreadLocalMap对象用来存储)。4)gt;removelt;/fontgt; 操作:从 ThreadLocalMap 中根据以当前 ThreadLocal 对象为 key 删除对应的Entry。
注:1、gt;ThreadLocallt;/fontgt; 的目的不是解决线程共享变量问题,而是隔离线程。每个线程访问的都是属于自己的变量,因此避免了线程安全问题。2、gt;ThreadLocallt;/fontgt; 适用于变量需要在线程间隔离而在方法间需要共享的场景。
内存泄漏问题
原因
解决办法
当我们使用完ThreadLocal变量的时候,记得调用其remove()方法,以避免内存泄漏
gt;J.U.Clt;/fontgt;
线程池gt; (Executor体系)lt;/fontgt;
线程池的作用
1、减少资源消耗。通过重复利用池中已创建的线程,减少频繁创建、销毁线程带来的资源消耗。2、提高响应速度。当线程池中有空闲线程,任务到来时无需创建线程就能立即被执行。 3、提高线程的可管理性。由线程池对池中的线程进行统一的管理和监控,可以防止无限制创建线程造成的资源浪费。
gt;Executorlt;/fontgt; 接口
其中只有一个gt; void execute(Runnable task)lt;/fontgt; 方法,用于执行任务
gt;ExecutorServicelt;/fontgt; 接口
gt;Executorlt;/fontgt; 接口的子接口
扩展了 gt;Executor lt;/fontgt;的功能,提供了管理线程的方法,并且提供了一系列能返回 gt;Futruelt;/fontgt; 对象的异步任务创建方法
常用方法
gt;shutdown()、shutdownNow()lt;/fontgt;
通过关闭线程池来拒绝处理新任务
gt;shutdown lt;/fontgt;与 gt;shutdownNow lt;/fontgt;的区别在于后者会立即关闭,不会等待正在执行的任务执行完毕
gt;isShutdown()、isTerminated()lt;/fontgt;
获取线程池是否被关闭的状态
gt;lt;Tgt; Futruelt;Tgt; submit(Callablelt;Tgt; task)lt;Tgt; Futruelt;?gt; submit(Runnable task)lt;Tgt; Futruelt;Tgt; submit(Runnable task,T result)lt;/fontgt;
向线程池中提交一个任务,并返回一个gt; Futrue lt;/fontgt;对象用于获取执行结果
gt;lt;Tgt; Listlt;Futurelt;Tgt;gt; invokeAll(Collectionlt;? extends Callablelt;Tgt;gt; tasks)lt;Tgt; T invokeAny(Collectionlt;? extends Callablelt;Tgt;gt; tasks)lt;/fontgt;
向线程池提交一个任务集合,执行集合中的一个或多个任务,并返回执行一个或多个 Futrue
gt;ThreadPoolExecutorlt;/fontgt; 类
是线程池的真正实现,通过在构造方法传入不同的配置参数来创建不同的线程池。
核心配置参数
gt;corePoolSizelt;/fontgt;
核心线程数
线程池中存活的最少线程数量,除非 gt;allowCoreThreadTimeoutlt;/fontgt; 属性值被设成 gt;truelt;/fontgt; 那么最少就是0
gt;maximumPoolSizelt;/fontgt;
最大线程数
线程池中最多能容纳的线程数量
gt;keepAliveTimelt;/fontgt;
指定空闲线程的存活时间,目的是为了减少资源消耗
在以下两种情况下生效:1、当线程池中线程数大于 gt;corePoolSizelt;/fontgt;,多余的空闲线程达到 gt;keepAliveTimelt;/fontgt; 指定的时间时会被停掉;2、gt;当 allowCoreThreadTimeOut 微软雅黑gt;值为lt;/fontgt; truelt;/fontgt;,核心线程空闲时间达到 gt;keepAliveTimelt;/fontgt; 指定的时间时也会被停掉
gt;workQueuelt;/fontgt;
任务队列,gt;BlockingQueue lt;/fontgt;类型
用于存放待执行的任务,可以是有界队列或无界队列
gt;handlerlt;/fontgt;
任务拒绝策略,gt;RejectedExecutionHandler lt;/fontgt;类型
用于处理线程池无法执行的任务
4种实现
gt;AbortPolicylt;/fontgt;
默认策略,直接抛出lt;code class=hljs java has-numberinggt; RejectedExecutionExceptionlt;/codegt; 异常
gt;CallerRunsPolicylt;/fontgt;
使用调用线程去处理任务
gt;DiscardPolicylt;/fontgt;
什么事都不干,相当于丢弃任务
gt;DiscardOldestPolicylt;/fontgt;
移除队列头部的任务,然后再次尝试执行当前任务
当以下两种情况发生时,就会执行任务拒绝策略: ① 任务队列满、同时达到最大线程数 gt;maximumPoolSizelt;/fontgt;;② 线程池已经被 gt;shutdownlt;/fontgt;,无法执行新任务。
工作原理
线程池被创建的时候里面是没有线程的,除非调用了gt; prestartAllCoreThreads()lt;/fontgt; 方法。1、当有任务需要执行并且线程池中线程数量小于gt; corePoolSize lt;/fontgt;时,新创建一个线程去执行这个任务。2、当线程数量达到gt; corePoolSizelt;/fontgt;,并且小于 gt;maximumPoolSizelt;/fontgt; 时,分两种情况:1)若任务队列未满,则将后续任务放入任务队列;2)若任务队列已满,则新创建一个线程去执行任务,直到达到最大线程数gt; maximumPoolSizelt;/fontgt;;3、达到最大线程数 gt;maximumPoolSizelt;/fontgt; 后,后续来的任务将会被执行拒绝任务策略。
gt;Fork/Joinlt;/fontgt; (多线程并行框架ForkJoinPool)
JDK1.7引入的lt;bgt;支持多核CPUlt;/bgt;的多线程并行框架,以充分利用多核CPU的优势。通过它可以实现多线程在多个CPU核心中并行处理任务。
利用分而治之的思想,将大任务分成小任务执行,然后合并结果;分别对应 fork、join 两个操作。
分支主题
gt;Fork/Joinlt;/fontgt; 框架的核心是 gt;ForkJoinPoollt;/fontgt; 类,它是对 gt;AbstractExecutorServicelt;/fontgt; 类的扩展。gt;ForkJoinPoollt;/fontgt; 实现了工作窃取(gt;work-stealinglt;/fontgt;)算法,并可以执行gt; ForkJoinTasklt;/fontgt; 任务。
gt;Executorslt;/fontgt; 工具类
包含一系列创建线程池的工厂(静态)方法,简化线程池的创建
常用方法
gt;Callablelt;Objectgt; callable(Runnable task)lt;Tgt; Callablelt;Tgt; callable(Runnable task, T result)lt;/fontgt;
包装 gt;Runnable 微软雅黑gt;类型的线程lt;/fontgt;lt;/fontgt;为 gt;Callable lt;/fontgt;类型
gt;newSingleThreadExecutor()lt;/fontgt;
创建一个只有单个工作线程的线程池,使用的队列是无界队列
gt;newFixedThreadPool(int nThreads)lt;/fontgt;
创建固定大小的线程池
gt;newCachedThreadPool()lt;/fontgt;
创建一个可根据需要创建线程的线程池,但是可以重用已经创建好的线程。注:它使用的是nbsp;gt;SynchronousQueuelt;/fontgt;作为任务队列
gt;newScheduledThreadPool(int corePoolSize)lt;/fontgt;
创建一个指定大小的线程池,用于定时或周期性的执行任务
gt;newSingleScheduledThreadExecutor()lt;/fontgt;
创建一个单线程化的线程池,用于定时或周期性的执行任务
gt;newWorkStealingPool()newWorkStealingPool(int parallelism)lt;/fontgt;
Java 8 新增,创建使用 gt;Fork/Joinlt;/fontgt; 框架执行任务的线程池
线程池大小配置
通过 gt;Runtime.getRuntime().availableProcessors()lt;/fontgt; 获得当前nbsp;gt;CPU lt;/fontgt;个数
1、如果是 CPU 密集型任务,就需要尽量压榨 CPU,参考值可以设为 NCPU + 1
+1原因:当线程因为偶尔的内存页失效或其他原因导致阻塞时,这个额外的线程可以顶上
2、如果是 IO 密集型任务,参考值可以设置为CPU 核数 * [ 1 +(I/O 耗时 / CPU 耗时)]
并发容器
gt;ConcurrentHashMaplt;/fontgt;
由于gt; HashMap 微软雅黑gt;在多线程并发 gt;put lt;/fontgt;操作情况下的 gt;rehashlt;/fontgt; 会导致死循环lt;/fontgt;lt;/fontgt;,Java 7 引入了分段锁技术 gt;Segmentlt;/fontgt; 来保证一定的线程安全,gt;Segmentlt;/fontgt; 继承于 gt;ReentrantLocklt;/fontgt;
为了进一步提高并发性及访问效率,Java 8 采用 gt;CASlt;/fontgt; 替代gt;微软雅黑gt;了lt;/fontgt; Segment,微软雅黑gt;同时lt;/fontgt;lt;/fontgt;加入了红黑树(当链表中结点数达到临界值8时就将其转成红黑树)
gt;BlockingQueuelt;/fontgt;
lt;bgt;阻塞队列:lt;/bgt;1)当队列为空时,获取元素的线程会等待,直到队列中有元素;2)当队列满时,存储元素的线程会等待,直到队列可以存储元素。通常用于生产者-消费者场景
gt;ArrayBlockingQueuelt;/fontgt;
有界队列,即队列容量有限,内部是数组结构
gt;LinkedBlockingQueuelt;/fontgt;
既可以是有界队列,又可以是无界队列,内部是单向链表结构。若创建时不指定容量,则默认是 gt;Integer.MAX_VALUElt;/fontgt;
gt;SynchronousQueuelt;/fontgt; (同步队列)
不保存元素,仅仅作为两个线程进行单向数据通信(如生产者/消费者场景)的临时通道
gt;PriorityBlockingQueuelt;/fontgt; (优先级阻塞队列)
属于无界队列,队列中的元素按优先级排列
使用了和 gt;PriorityQueuelt;/fontgt; 一样的排序规则,同时具备 gt;BlockingQueuelt;/fontgt; 的特性
通过在构造方法传入 gt;Comparatorlt;/fontgt; 比较器来决定元素顺序;如果不提供比较器,则采用自然排序(即通过 gt;Comparable微软雅黑gt; 的 lt;/fontgt;compareTo微软雅黑gt;方法,因此插入队列中的元素必须实现 gt;Comparablelt;/fontgt; 接口)lt;/fontgt;lt;/fontgt;
gt;CopyOnWriteArrayListlt;/fontgt;
从字面上看就是”写时复制“。原理是当需要对集合中元素进行增删改操作时首先复制一个副本,对副本进行操作
适用于“读多写少”的并发场景
......
并发工具
ReentrantLock
Lock 用于解决互斥问题,Condition 用于解决同步问题。
用锁的最佳实践
1.永远只在更新对象的成员变量时加锁
2.永远只在访问可变的成员变量时加锁
3.永远不在调用其他对象的方法时加锁
gt;CountDownLatchlt;/fontgt; (倒计数器)
利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他几个任务执行完毕之后才能执行,就可以使用gt; CountDownLatch lt;/fontgt;来实现。
内部使用了 gt;AQS lt;/fontgt;的共享锁机制
两个关键方法:1. gt;await() 微软雅黑gt;当前线程lt;/fontgt;lt;/fontgt;等待其它线程执行完毕(即计数器减到0),当前线程恢复执行;2. gt;countDown()lt;/fontgt; 当有一个线程执行完毕就将计数器减1,直至减到0。
应用场景
模拟多个线程并发
gt;CyclicBarrierlt;/fontgt;(回环栅栏)
通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,gt;CyclicBarrier lt;/fontgt;可以被重用(通过调用 gt;reset() lt;/fontgt;方法)。
gt;await() lt;/fontgt;方法,此方法作用是等待其它线程都到同一状态时开始同时执行。类似于长跑比赛中,当所有运动员都准备完毕才开始比赛。
gt;Semaphore 微软雅黑gt;(lt;/fontgt;lt;/fontgt;信号量)
用于限制访问的最大线程数
维护一个访问许可集,其大小由初始化时的构造器参数指定。
主要方法
通过gt; acquire lt;/fontgt;和 gt;release lt;/fontgt;方法获取和释放访问许可。其获取的方式有公平和非公平两种,默认是非公平
gt;acquire(int permits)lt;/fontgt;、gt;tryAcquire(int permits)lt;/fontgt;
gt;微软雅黑gt;获取指定数量的访问许可,相当于在可用的数量上减去lt;/fontgt;lt;/fontgt;
gt;release(intlt;/fontgt;gt;gt; permitslt;/fontgt;)、tryRelease(intlt;/fontgt;gt;gt; permitslt;/fontgt;) lt;/fontgt;
gt;微软雅黑gt;gt;微软雅黑gt;释放指定数量的访问许可,lt;/fontgt;lt;/fontgt;相当于在可用的数量上增加lt;/fontgt;lt;/fontgt;
gt;Exchanger 微软雅黑gt;(交换器lt;/fontgt;)lt;/fontgt;
用于成对的线程间进行数据交换,可以看成是一个双向的 gt;SynchronousQueuelt;/fontgt;
gt;V exchange(V x)lt;/fontgt; 方法
gt;V exchange(V x, long time, TimeUnit unit) 微软雅黑gt;方法lt;/fontgt;lt;/fontgt;
原子操作类 (atomic子包)
位于 gt;java.util.concurrent.atomiclt;/fontgt; 包,使用 gt;CASlt;/fontgt; 实现的原子操作。
基本类型
如 AtomicInteger 、AtomicLong 、AtomicDouble 等
数组类型
如 AtomicIntegerArray、AutomicDoubleArray等
引用类型
如AtomicReference、AtomicReferenceFieldUpdater等
比 gt;synchronized lt;/fontgt;控制的粒度更细、量级更轻(所采用的自旋锁是一种轻量级锁),并且在多核处理器具有高性能
Java 8 又增加了四个类
gt;DoubleAccumulator、lt;/fontgt;gt;DoubleAdder、LongAccumulator、LongAdderlt;/fontgt;
lt;bgt;弥补原有的 Atomic 系列类的不足:lt;/bgt;虽然通过CAS保证了并发时操作的原子性,但是在高并发情况下,由于存在自旋锁(spin lock)机制,会导致CAS的失败次数会增多,从而引起更多线程的重试,最后导致效率降低。
gt;LongAdder VS AtomicLonglt;/fontgt;
较低并发时两个类效果差不多,而高并发时使用gt; LongAdderlt;/fontgt; 更高效,但也会消耗更多的空间
显示锁 (locks子包)
gt;Condition/ConditionObjectlt;/fontgt;
提供了类似 gt;Objectlt;/fontgt; 类中gt; wait、notify、notifyAlllt;/fontgt; 的方法,主要包括 gt;await、signal、signalAlllt;/fontgt; 方法
与 gt;synchronizedlt;/fontgt; 和 gt;wait、notify、notifyAll lt;/fontgt;的搭配类似,这些方法与gt; Locklt;/fontgt; 锁配合使用也可以实现等待/通知机制
gt;LockSupportlt;/fontgt;工具类
用于操作线程,是锁和同步类的基础。基于 gt;Unsafe 微软雅黑gt;中的lt;/fontgt; park、unpark lt;/fontgt;实现
用来代替 gt;wait、notity、notifyAlllt;/fontgt; 。因为 gt;LockSupport lt;/fontgt;对gt; park lt;/fontgt;方法和 gt;unpark lt;/fontgt;方法的调用没有先后的限制,而gt; wait 微软雅黑gt;方法则lt;/fontgt;lt;/fontgt;必须保证在 gt;notify/notifyAll lt;/fontgt;之前被调用。
gt;park()、parkNanos(long timeout)、parkUntil(long deadLine) lt;/fontgt;方法表示将当前线程挂起,后两个表示挂起一段时间
gt;unpark(Thread thread) lt;/fontgt;方法取消指定线程 gt;thread lt;/fontgt;挂起
JDK 1.6 后增加了一个方法参数 gt;blockerlt;/fontgt;,表示锁的对象,在通过线程监控和分析工具来定位问题时很有用
gt;AQSlt;/fontgt; 同步器
gt;AbstractQueuedSynchronizerlt;/fontgt; 类的简称
lt;bgt;实现了线程同步的语义,是 gt;Java lt;/fontgt;中gt; Locklt;/fontgt; 锁机制的基础。lt;/bgt;通过它可以很方便的实现同步器。lt;bgt;lt;/bgt;
核心思想:1、基于gt; volatilelt;/fontgt; 类型的gt; state lt;/fontgt;变量,配合 gt;Unsafelt;/fontgt; 类的 gt;CAS lt;/fontgt;操作来实现对当前锁状态 gt;statelt;/fontgt; 进行修改;2、内部依赖一个双向队列来完成资源获取线程的排队工作。
其中定义了两种获取锁的方式
共享方式 gt;SHAREDlt;/fontgt;
同一时间可以有多个线程获得锁,多个线程共享一把锁
采用共享方式得到的锁即为lt;bgt;共享锁lt;/bgt;
适用于多个线程同时进行读操作场景
排他方式 gt;EXCLUSIVElt;/fontgt;
同一时间只能有一个线程获取到锁
采用排他方式得到的锁即为lt;bgt;排他锁,也称为独占锁lt;/bgt;
适用于多个线程同时进行写操作时同步
gt;Locklt;/fontgt;nbsp;体系
gt;JDKlt;/fontgt; 层面实现,比起 gt;synchronizedlt;/fontgt; 可控性更强,弥补了 gt;synchronizedlt;/fontgt; 的不足。lt;bgt;使用后必须手动释放锁,否则可能会导致死锁lt;/bgt;
gt;locklt;/fontgt;:如果没获取到,会一直阻塞直到成功获取到锁;gt;tryLock:lt;/fontgt;尝试获取锁,获取到则返回true;如果没获取到则返回false,不会一直阻塞,可以指定等待时间;gt;unlocklt;/fontgt;:释放锁。lt;bgt;都需要显示调用lt;/bgt;。
典型用法:lock.lock();try {nbsp; // do something with lock} finally {nbsp; lock.unlock();}
gt;ReentrantLocklt;/fontgt; 可重入锁
包括公平锁 gt;FairSynclt;/fontgt;、非公平锁gt; NonfairSync lt;/fontgt;两种实现,默认是非公平
可重入锁:是指当线程在获取到锁之后,再次获取该锁而不会被该锁所阻塞;公平锁:是指线程按请求获取锁的先后顺序获取锁(排队);非公平锁:则允许在线程发出请求后立即尝试获取锁(抢占),如果尝试失败才进行排队等待
可重入实现原理:1、其中包含一个 Thread 类型的成员变量 exclusiveOwnerThread,表示已经获取到该锁的线程,以及一个计数器;2、当线程获取锁成功后,该成员变量即赋值为该线程,同时计数器加一;3、下一次再获取锁的时候,会先判断已经获取到锁的线程是否是当前线程,如果是则直接获取,同时计数器加一;4、每一次释放锁时都将计数器减一,直到减为0,将nbsp;exclusiveOwnerThread 置为null,表示当前没有线程持有该锁。
gt;ReadWriteLock lt;/fontgt;读写锁
允许多个线程对资源同时进行读操作,或一个线程写操作,但读和写操作二者不能同时发生
gt;ReadLocklt;/fontgt; 读锁
属于共享锁实现
gt;WriteLock lt;/fontgt;写锁
属于排他锁实现
StampedLock
是Java 8引入的锁,用于解决原有 ReadLock 的读操作阻塞写操作的问题。
支持三种模式的锁操作。分别是: 写(Writing)、读(Reading)、乐观读 (Optimistic Reading)。其中lt;bgt;写模式获取的是互斥锁,而读模式不是lt;/bgt;。
lt;bgt;由于它支持多种锁模式协调使用,因此并没有直接实现 Lock 或 ReadWriteLock 接口。lt;/bgt;
主要方法
锁的获取和释放
写锁:writeLock()/tryWriteLock()/tryWriteLock(time, unit)、unlockWrite(stamp)
(悲观)读锁:readLock()/tryReadLock()/tryReadLock(time, unit)、unlockRead(stamp)
乐观读锁:tryOptimisticRead()
unlock(stamp)
锁模式切换
tryConvertToWriteLock(stamp)
tryConvertToReadLock(stamp)
tryConvertToOptimisticRead(stamp)
线程与进程
线程是程序的最小执行单元
进程是操作系统进行资源分配和调度的一个基本单位
关联:一个程序至少有一个进程,一个进程又至少包含一个线程。进程中的多个线程共享进程的资源
引入线程的目的:充分利用 gt;CPU lt;/fontgt;资源,使其可以并行处理多个任务,减少时间消耗,提高效率
线程分类
用户线程 gt;User Threadlt;/fontgt;
一般是程序中创建的线程
守护线程 gt;Daemon Threadlt;/fontgt;
为用户线程服务的线程,当所有用户线程结束时才会被终止。如JVM的垃圾回收。
通过 gt;Threadlt;/fontgt; 的 gt;setDaemon(true)lt;/fontgt; 方法将一个用户线程变成守护线程
线程的生命周期
六种状态
新建状态 gt;Newlt;/fontgt;
gt;Threadlt;/fontgt; 类
实现了gt; Runnable lt;/fontgt;接口
gt;runlt;/fontgt; 方法,无返回值
gt;Runnablelt;/fontgt; 接口
gt;runlt;/fontgt; 方法,无返回值,通过gt; Threadlt;/fontgt; 类或线程池来使用
gt;Callablelt;/fontgt; 接口
作为gt; FutureTasklt;/fontgt; 构造方法参数使用
gt;call lt;/fontgt;方法,有返回值,且可以抛出异常
call方法实际是在 gt;Runnablelt;/fontgt; 的 gt;runlt;/fontgt; 方法中被执行的
就绪状态gt; Runnablelt;/fontgt;
调用gt;微软雅黑gt;新建线程的 lt;/fontgt;start()lt;/fontgt; 方法
不一定会立即运行,可能需要等待gt; CPUlt;/fontgt; 分配时间片
阻塞状态gt; Blockedlt;/fontgt;
线程等待synchronized隐式锁的状态
等待
发生在调用以下几个方法时:lt;ulgt;lt;ligt;不带参数的gt; Object.wait()lt;/fontgt;lt;/ligt;lt;ligt;不带参数的gt; Thread.join()lt;/fontgt;lt;/ligt;lt;ligt;gt;LockSupport.park()lt;/fontgt;lt;/ligt;lt;/ulgt;
超时等待
与 状态不同在于不会一直等待,而是等待指定的时间
发生在调用以下几个方法时:lt;ulgt;lt;ligt;gt;Thread.sleep(long millis)lt;/fontgt;lt;/ligt;lt;ligt;gt;Object.wait(long timeout)lt;/fontgt;lt;/ligt;lt;ligt;gt;Thread.join(long timeout) lt;/fontgt;lt;/ligt;lt;ligt;gt;LockSupport.parkNanos()lt;/fontgt;lt;/ligt;lt;ligt;gt;LockSupport.parkUntil()lt;/fontgt;lt;/ligt;lt;/ulgt;
终结状态
当线程运行完毕,即死亡
其他知识点
只要线程当前处于BLOCKED、WAITING、TIMED_WAITING 其中一种状态,线程就没有CPU的使用权
图片
分支主题
类的常用方法
gt;Thread.sleep(long millseconds) lt;/fontgt;方法
1、gt;sleep lt;/fontgt;方法是 类的静态方法,是为了保证该操作只对当前线程有效,避免线程安全问题,其它几个常用静态方法类似。2lt;codegt;微软雅黑gt;、让当前正在运行的线程lt;/fontgt;lt;/codegt;微软雅黑gt;lt;/fontgt;暂时停止运行,一段时间后会继续执行。
gt;Thread.yield() lt;/fontgt;方法
当前处于运行状态的线程主动放弃占用的CPU资源,转变为就绪状态,让其他先线程执行(让步)
gt;join lt;/fontgt;方法
在当前线程执行过程中引入另一个线程,并且当前线程需要等待另一个线程执行完毕后才能继续执行
可用于实现多个线程顺序执行。如 t.join() 表示将当前线程阻塞,直到线程 t 执行完成当前线程才能继续执行
gt;Thread.currentThread() lt;/fontgt;方法
获取当前正在运行的线程
线程间通信
共享变量
等待/通知机制
gt;Objectlt;/fontgt; 的对象监视器方法
lt;spangt;这三个方法依赖于对象监视器,所以必须在 gt;synchronizedlt;/fontgt; 语句块内使用,否则会抛出 lt;/spangt;lt;spangt;gt;IllegalMonitorStateExceptionlt;emgt;lt;/emgt;lt;/fontgt; 异常。lt;/spangt;
gt;wait lt;/fontgt;方法
使得当前线程必须要等待(阻塞状态),等到另外一个线程调用notify()或者notifyAll()方法。
gt;notifylt;/fontgt; 方法
唤醒一个等待当前对象的锁的线程(一般是高优先级的线程)开始排队
gt;notifyAll lt;/fontgt;方法
方法会唤醒其它所有等待当前对象的锁的线程
gt;Lock、Conditionlt;/fontgt;
gt;Lock微软雅黑gt; 的 lt;/fontgt;lock、tryLock、unlock、getCondition lt;/fontgt;等方法
gt;Condition 微软雅黑gt;的lt;/fontgt; await、signal、signalAlllt;/fontgt; 方法
使用 gt;volatilelt;/fontgt; 关键字
数据传递
gt;PipedInputStream/PipedOutputStream lt;/fontgt;管道流
gt;BlockingQueue 微软雅黑gt;阻塞队列lt;/fontgt;lt;/fontgt;
gt;Exchangerlt;/fontgt; 交换器
停止线程的方法
gt;Thread.stop lt;/fontgt;方法:已废弃
使用一个标识来表示线程的状态,通过更改它的值来控制线程的运行和停止
gt;interruptlt;/fontgt; 中断方法
同步关键字
gt;synchronizedlt;/fontgt;
在 gt;JVMlt;/fontgt; 层面实现了对临界资源的同步互斥访问,锁的释放不用人工干预,由虚拟机自动完成。
1、在 gt;volatile lt;/fontgt;基础上增加了互斥锁,所谓“互斥”就是同一时间只能有一个线程操作该资源;2、lt;spangt;在 JDK 1.5 版本以后,为了弥补gt; synchronized lt;/fontgt;的不足,引入了gt; Lock lt;/fontgt;来代替它,将同步锁对象换成了 gt;Conditionlt;/fontgt; 对象,并且 gt;Condition lt;/fontgt;对象可以有多个。lt;/spangt;
用法
同步代码块
通常将外界资源作为锁的对象
gt;synchronized(obj) {nbsp;nbsp; // 同步操作代码}lt;/fontgt;
用于保护外界资源不被多个线程并发修改
与同步方法比较而言,使用同步代码块的好处在于其他线程仍可以访问同步代码块以外的代码
同步方法
锁的对象是当前对象this
gt;public synchronized void test() {nbsp; // 同步操作代码}lt;/fontgt;
用于保护对象属性值不会被多个线程并发修改
同步静态方法
锁的对象是类的Class对象
gt;public static synchronized void test() {nbsp;nbsp; // 同步代码}lt;/fontgt;
用于保护类的静态属性值不会被多个线程并发修改
lt;bgt;缺点:lt;/bgt;1、无法知道是否成功获取到锁2、如果是多个线程需要同时进行读操作,一个线程读操作时其它线程只有等待
lt;bgt;注:为了确保所有线程都能看到共享变量的最新值,因此所有执行读操作或写操作的线程都必须在同一个锁上同步。lt;/bgt;
gt;volatilelt;/fontgt;
线程每次都从主内存中读取变量,改变后再写回到主内存。其作用如下:1、使变量在多个线程间具有可见性(volatile 变量不会被缓存到寄存器或其他对处理器不可见的地方)2、禁止 gt;JVMlt;/fontgt; 对该变量的操作进行指令重排序(保证有序性)
使用条件
1、对变量的写操作不依赖于变量的当前值,或者保证仅有一个线程对变量进行写操作;2、该变量不会和其他状态变量一起被纳入不变性条件中;3、访问变量不需要加锁。
应用场景
一写多读,保证变量可见性
开销较低的读写锁策略
将变量使用 gt;volatile lt;/fontgt;关键字修饰,只在多个线程同时需要进行写操作时加锁,读操作不需要
加写锁方式
gt;synchronized lt;/fontgt;关键字
使用 gt;Locklt;/fontgt;
gt;volatile VS synchronizedlt;/fontgt;
gt;微软雅黑gt;访问lt;/fontgt; volatile 微软雅黑gt;变量不需要加锁,lt;/fontgt;lt;/fontgt;gt;微软雅黑gt;gt;微软雅黑gt;因此不会阻塞线程,因此它lt;/fontgt;lt;/fontgt;是比 lt;/fontgt;synchronized 微软雅黑gt;更轻量级的同步机制。lt;/fontgt;微软雅黑gt;但是 lt;/fontgt;volatilelt;/fontgt; 不能完全替代 gt;synchronizedlt;/fontgt;,因为它lt;bgt;不能保证原子性,非线程安全,lt;/bgt;而加锁机制既可以确保可见性,又可以确保原子性。
线程死锁
死锁产生的四个必要条件
lt;ulgt;lt;ligt;互斥条件:资源不能被共享。即任一时刻一个资源只能给一个进程使用,其他进程只能等待,直到资源被占有者释放。lt;/ligt;lt;ligt;不可剥夺条件:已经分配的资源不能从相应的进程中被强制地剥夺,而只能由获得该资源的进程自愿释放。lt;/ligt;lt;ligt;请求和保持条件:已经得到资源的进程可以再次申请新的资源。lt;/ligt;lt;ligt;循环等待条件:系统中若干进程组成环路,该环路中每个进程都在等待相邻进程正占用的资源。 lt;/ligt;lt;/ulgt;
预防死锁的方法
合理对资源进行动态分配,以避免死锁
破坏死锁产生的四个必要条件
定时任务
gt;Timer TimerTask lt;/fontgt;类
gt;Timerlt;/fontgt;:任务调度器,通过lt;codegt;schedule(TimerTask task, long delay)等方法进行调度。lt;/codegt;gt;TimerTasklt;/fontgt;:实现了 gt;Runnablelt;/fontgt; 接口。表示需要调度的任务,里面有一个run方法定义具体的任务。
gt;Timerlt;/fontgt; 缺点:内部是单线程,因此如果有异常产生,线程将退出,整个定时任务就会失败
线程池
gt;ScheduledExecutorServicelt;/fontgt;,它是 gt;ExecutorService lt;/fontgt;的子接口。弥补了 gt;Timerlt;/fontgt; 的缺陷
通过gt;scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)lt;/fontgt;和gt;scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)lt;/fontgt;等方法来实现定时任务调度
异步任务
只需创建并启动一个线程执行该任务即可,如果需要获取执行结果,则用Callable。也可以使用线程池
Callablelt;Vgt; 接口
表示一个带有返回结果的任务
通常通过 ExecutorService 来使用
Futurelt;Vgt; 接口
包装异步任务的返回结果
常用方法
boolean isDone():用于判断任务是否执行完成
V get():获取执行结果
FutureTask
实现了Future和Runnable接口,表示一个异步任务
CompletableFuture
0 条评论
下一页