多线程笔记

源码地址

避免使用同步的方式
一:线程封闭
线程封闭:访问共享的可变数据时,需要使用同步,避免使用同步的方式就是不共享数据,仅是单线程访问数据不需要同步,
当某个对象封装在一个线程中,这种用法将自动实现线程安全性,即使被封闭的对象本身不是线程安全的
如:swing/jdbc的connection对象/局部变量/ThreadLocal

Ad-hoc线程封闭:维护线程封闭的职责完全由程序实现来承担。
栈封闭(线程内部(局部)使用):只能通过局部变量才能访问对象
对于基本数据类型无法获得引用,所以确保了封闭在线程中
使用ThreadLocal维持线程封闭

二:使用不可变对象

编写线程安全的代码:在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问
当多个线程访问同一个可变的状态变量时没有使用合适的同步,3种方式修复
1:不在线程之间共享该状态
2:状态变量修改不可变
3:访问状态变量时使用同步
线程安全性:当多个线程访问某个类时,这个类始终都能表现正确的行为。
竞态条件:基于一种可能失效的观察结果做出判断或者执行某个计算。(先检查后执行)
解决办法:在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量,
从而确保其他线程只能在修改操作完成之前或之后读取和修改状态,而不是在修改状态的过程中
要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量
尽量将不影响共享状态且执行时间较长的操作从同步代码块中分离出去,从而在这些操作的执行过程中,其他线程可以访问共享状态
执行时间长的操作,不要持有锁
最低安全性:当线程在没有同步的情况下读取变量时,可能会得到一个失效值,但至少这个值是由之前某个线程设置的值,而不是随机值

long和double,jvm允许将64位读写分解2个32位操作,不安全,解决办法,用volatile声明or锁保护
加锁:可以互斥,以及内存可见性,为了确保所有线程都能看到共享变量的最新值,所有执行读or写操作的线程都必须在同一个锁上同步

重入:某个线程获得一个自己持有的锁会成功,如子类加锁方法访问父类加锁的方法。获取锁的操作的粒度是线程,而不是调用

volatile:申明变量共享,编译器不会把该变量与其他内存操作重排序。不会被缓存寄存器或者其他,因此读取会返回最新值
执行时,不会添加锁,所以不会线程阻塞
如果在验证正确性需要对可见性进行复杂判断,不要使用。
作用包括,确保自身可见性,他们所引用对象的可见性,标识重要程序生命周期事件的发生(初始化or关闭)
只能确保可见性,无法确保原子性
满足以下条件才使用

        对变量的写入操作不依赖变量当前值,或者你能确保只有单个线程更新变量值
           当只有单个值修改时,值修改后,其他内存过期.由于可见性,另外一个线程直接主内存获取最新的值
        该变量不会与其他状态变量一起纳入不变性条件中
            A线程修改时,B线程修改的时候,重复修改操作,B在A修改前值赋予给了寄存器,且进行了操作.这个时候A执行成功,B因为获取值了不会再次获取
        访问变量时不需要加锁

发布一个对象:使对象能够在当前作用域之外的代码中使用
1:将对象引用保存到公有静态变量中
2:对象传递给某个外部方法(行为并不完全由自己规定的方法,包括其他类以及自身非private/final方法)
3:发布一个内部类实例,参考L_ThisEscape
逸出:不应该发布的对象被发布

不可变对象:某个对象在被创建后其状态不能修改
不可变性不等于将对象中所有域声明为final类型,即便对象所有域都是final,但是因为在final域中可以保存对可变对象的引用
满足以下条件,对象才不可变
1:对象创建以后其状态就不能修改
2:对象的所有域都是fianl
3:对象正确创建,this没有逸出
N_ThreeStooges/O_VolatileCachedFactorizer

安全发布
P_Holder

不可变对象与初始化安全性
即便某个对象的引用对其他线程是可见的,并不意味着对象状态对于使用该对象的线程来说一定可见。确保一直使用同步
即便发布不可变对象的引用没有使用同步,也依然可以安全访问该对象。如果final类型的域指向的对象是可变对象,访问这些域所指向的对象的状态时依然需要同步
java为不可变对象的共享提供了特殊的初始化安全性保证,为了维持这种保证,必须满足不可变性的所有需求
1:状态不可修改
2:所有域都是final
3:正确构造过程
P_Holder对象如果是不可变的,即便没有被正确发布,assertSanity也不会异常

安全发布常用模式
要安全地发布一个对象,对象的引用以及状态必须同时对其他线程可见
1:静态初始化函数中初始化一个对象引用,静态初始化,静态初始化由jvm在类初始化阶段执行,jvm内部存在同步机制,所以可以安全发布
2:对象的引用保存volatile类型的域或者AtomicReferance对象中
3:对象的引用保存到某个正确构造对象的final类型域中
4:对象的引用保存到一个由锁保护的域中
以下可以安全发布对象
map:Hashtable/synchronizedMap/ConcurrentMap
list:Vector/CopyOnWriteArrayList/CopyOnWriteArraySet/synchronizedList/synchronizedSet
queue:BlockingQueue/ConcurrentLinkedQueue
Future/Exchanger(数据传送机制)

事实不可变对象
定义:对象从技术上来看是可变的,但其状态在发布后不会再改变
在没有额外的同步情况下,任何线程都可以安全使用被安全发布的事实不可变对象

可变对象
对于可变对象,发布对象时需要使用同步,并且在每次对象访问时同样需要使用同步来确保后续修改操作的可见性,
对象的发布需求取决于他的可变性
1:不可变对象任意机制发布
2:事实不可变对象必须通过安全方式发布
3:可变对象必须通过安全方式发布,并且必须是线程安全的或者由某个锁保护起来

安全地共享对象
并发程序使用共享对象,常见策略
线程封闭:线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能有这个线程修改
只读共享:在没有额外同步的情况下,共享的只读对象可以有多个线程并发访问,但任何线程都不能修改,共享的只读对象包括不可变对象和事实不可变对象
线程安全共享:线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口来进行访问而不需要进一步的同步
保护对象:被保护的对象只能通过持有特定的锁来访问,保护对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象

设计线程安全类
设计线程安全类的过程中,应包含下列基本要素:
1:找出构成对象状态的所有变量
2:找出约束状态变量的不变性条件
3:建立对象状态的并发访问管理策略
如果对象的域引用了其他对象,那么该对象的状态将包含被引用对象的域

收集同步需求
要想确保线程安全,首先需要了解对象不变性条件(判断状态是否有效)与后验条件(判断状态迁移是否有效,例如17++后值等不等于为18)
要满足在状态变量的有效值或状态转换上的各种约束条件,需要借助原子性和封装性

依赖状态
定义:在某个操作中包含有基于状态的先验条件。例如移除元素时判断队列是否为空

如果一个状态变量是线程安全的,并且没有任何不变性条件来约束他的值,在变量的操作上也不存在任何不允许的状态转换,那么可以安全发布这个变量

放低期望,如若某个类没有声明是线程安全的,假设不安全
猜测一个类是否安全,从实现者角度解释规范,
HttpSession/ServletContext基本服务多线程的,假设线程安全。在规范和案例使用中发现假设正确。另一方面,session里面的对象需要复制和钝化,要保证这些对象线程安全
jdbc DataSource对单线程没有意义,多线程里面才有意义,则假设实现了同步,但是另一方面在返回连接池之前,不会有其他操作共享。基于这2点,jdbc Connection跨越多个线程则需要同步(大多数则只封闭特定线程中)

同步容器类
Vector/Hashtable/Collections.synchronizedXxx等类
将所有对容器状态的访问都串行化,以实现线程安全性。严重降低并发性,吞吐。
有些复合操作需要加锁
迭代元素时,元素被另一个线程修改,抛出ConcurrentModificationException。
解决办法:
1:迭代时,添加锁.会产生死锁,降低吞吐和cpu利用率
2:克隆,在副本进行迭代.克隆过程依然需要加锁.好坏取决于,容器大小,响应时间等

并发容器
ConcurrentHashMap/CopyOnWriteArrayList(替代同步List)/CopyOnWriteArraySet(替代同步Set)
ConcurrentLinkedQueue/ConcurrentSkipListMap/ListSet(替代SortedMap/Set)
提高伸缩性并降低风险
替代同步容器,同时支持一些常见复合操作,如没有则添加,替换,有条件删除等
分段锁,弱一致性,size/isEmpty返回近视值
ConcurrentHashMap不能被加锁来执行独占访问,因此无法使用客户端加锁来创建新的原子操作
CopyOnWriteArrayList:写入时复制,只要正确发布一个事实不可变对象,在访问该对象时就不再需要进一步同步,在每次修改时,都会创建并重新发布一个新的容器副本,从而实现可变性.
使用场景:迭代>修改,事件通知系统(注册和注销事件监听器操作少于接收事件通知的操作)
阻塞队列和生产者
LinkBlockingQueue/ArrayBlockingQueue FIFO
PriorityBlockingQueue 优先级队列
SynchronousQueue 维护一组线程,这些线程在等待着把元素加入or移出队列,有消息立刻通知对方,而不是放入队列接着执行下一个事
降低数据从生产者移动到消费者的延迟
场景:有足够多的消费者,总有一个消费者准备好交付工作,才适用
串行线程封闭
对于可变对象,生产者-消费者+阻塞队列,促进串行线程封闭,从而将对象所有权从生产者交付给消费者。
双端队列,jdk6添加了Deque/BlockingDeque对Queue和BlockQueue进行扩展
场景:工作密取
每个消费者都有各自的双端队列,如果一个消费者完成了自己双端队列中的全部工作,那么他可以从其他消费者双端队列末尾秘密获取工作。
与生产者消费者比,工作线程不会再单个共享的任务队列发生竞争。大多数情况只访问自己的双端队列,极大少竞争。
工作队列需要访问另一个队列时,会从队列尾端而不是头部获取工作,进一步降低了队列上的竞争

阻塞方法与中断方法
抛出InterruptedException,表示该方法是一个阻塞方法,如果这个方法中断,他将努力提前结束阻塞状态
选择1:往上传递InterruptedException
选择2:恢复中断,如ZE_TaskRunnable

同步工具类
可以是任何对象,只要它根据其自身的状态来协调线程的控制流
阻塞队列/信号量/栅栏/闭锁
封装一些状态,这些状态将决定执行同步工具类的线程是继续执行还是等待
闭锁:延迟线程的进度直到其到达终止状态。确保某些活动直到其它活动都完成后才继续运行。发令枪响后,所有运动员跑步
ZF_TestHarness
使用FutureTask提前加载稍后需要的数据
ZG_Preloader
计数信号量:控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量,实现某种资源池,对容器施加边界
ZH_BoundedHashSet
栅栏:阻塞一组线程直到某件事发生,可重复使用,另一种栅栏式。5个人约好集合后去其他地方。
ZI_CellularAutomata
2方栅栏(Exchange):一个人有零食,另一个人有钱,他们两个想等价交换,对好口号在某个地方相见,一个人先到了之后,必须等另一个人带着需要的东西来了之后,才能开始交换。

构建高效可伸缩的结果缓存
ZJ_Memoizer
总结
可变状态是至关重要的,所有的并发问题都可以归结为如何协调对并发状态的访问,可变状态越少,越容易确保线程安全性
域尽量声明final
不可变对象一定是线程安全的
封装有助于管理复杂性,尽量不要将数据保存在全局变量中
锁保护每个可变对象
当保护同一个不变性条件中的所有变量时,要使用同一把锁
执行复合操作期间,要持有锁

----------------------------第二部分-结构化并发应用程序-----------------------------------------------
串行:吞吐低 A_SingleThreadWebServer
并发: B_ThreadPerTaskWebServer
1:响应快
2:异步处理提升吞吐
3:要求任务处理代码线程安全
无限制创建线程不足
线程生命周期开销高
资源消耗
稳定性:受各方面的影响(jvm启动参数,thread构造栈大小等),创建的线程数量存在限制。超过限制则内存溢出
解决办法使用Executor
Executor基于生产者消费者模式,提交任务操作相当于生产者,执行任务的线程相当于消费者
C_TaskExecutionWebServer
ExecutorService:用于生命周期管理
延迟任务与周期任务
Timer所有定时任务只会创建一个线程
采用ScheduledThreadPoolExecutor
构建自己调度可用DelayQueue
携带结果的任务Callable与Future
渲染页面案例
1:遇text加载text,遇图片加载图片
2:先加载text,在加载图片 F_SingleThreadRenderer
3:另开一个线程预先下载图片,加载text,加载图片 F_FutureRenderer
3:为每一幅图片创建下载任务,在线程池中执行,加载text,从CompletionService获得加载完成的图片,然后加载 F_Renderer
4:设置超时任务,超时没下载的给予默认图片
Future.get(long timeout, TimeUnit unit)
5:多个任务设置超时执行
ExecutorService.invokeAll(Collection<? extends Callable> tasks,long timeout, TimeUnit unit)

取消和中断
一个可取消的任务必须拥有取消策略,即如何取消任务(how),任务啥时候检测是否已经取消了请求(when),以及响应取消请求时执行那些操作(what)
J-O
停止基于线程的服务
除非拥有某个线程,否则不能对该线程操作。
应用程序可以拥有服务,但是不能拥有工作者线程。
应用程序关闭服务,服务关闭线程。
对于持有线程的服务,只要服务的存在时间>创建线程的方法的存在时间,那么就应该提供生命周期方法。
日志关闭案例
P-R
关闭ExecutorService
S
毒丸对象
T
某个方法处理一批任务,当所有任务处理后才返回
U
使用V_TrackingExecutor来保存未完成的任务以备后续执行
V
主动解决未检查异常
W
未捕获异常的处理
X
ThreadPoolExcutor构造提供ThreadFactory(设置UncaughtExceptionHandler)
executor提交的任务才能抛出未捕获异常
submit抛出的异常会在get时重新抛出
jvm关闭
Y
守护线程
场景:执行辅助工作,不希望影响jvm关闭
jvm启动时创建的所有线程,除了主线程都是守护线程。主线程创建的所有线程都是普通线程
jvm停止时,所有依然存在的守护线程都会被抛弃,不执行finally,jvm直接退出
少用守护线程

线程池的使用
线程饥饿死锁
Z_ThreadDeadlock
运行时间较长的任务
如果线程池中线程的数量远小于在稳定状态下执行时间较长任务的数量,那么到最后可能所有的线程都会运行执行时间较长的任务,从而影响整体响应性
解决:限定任务等待资源的时间,扩充线程池大小。
设置线程池大小
线程池过大,造成大量线程在相对很小的cpu和内存资源上发生竞争,会导致内存过高
线程池过小,许多空闲处理器无法工作,降低吞吐
需要考虑,cpu,内存,任务是计算密集类型还是io密集还是2者,
如果需要执行不同类别任务,并且之间行为相差很大,考虑使用多个线程池,从而使每个线程池可以根据各自的工作负载来调整
计算密集型,线程大小为N+1(多一个备份)
包含I/O或者其他阻塞任务
N=cpu个数 Runtime.availableProcessors
U=CPU利用率 0<=U<=1
W/C=任务等待时间/计算时间
线程池最优大小=NU(1+W/C)
资源:该资源的可用总量/每个任务对该资源的需求量
可根据ThreadPoolExecutor定制线程池大小
线程创建与销毁
线程池基本大小,最大大小,存活时间等负责线程创建与销毁
创建ThreadPoolExecutor初期,有任务提交时才会启动线程,除非调用prestartAllCoreThreads
工作队列满了才会创建超过基本大小的线程数,线程数为0时,也是如此
线程空闲时间>存活时间,被标记可回收,并且当线程池当前大小超过基本大小,线程被终止
allowCoreThreadTimeOut使线程所有线程超时,可以使用这个特性+基本大小设置为0,可在没有任务情况下销毁所有线程
newFixedThreadPool:创建的线程池不会超时
newCachedThreadPool:线程最大设置Integer.MAX,基本大小=0,超时设置1分钟
管理队列任务
无界队列(newFixedThreadPool/newSingleThreadPool):内存耗尽
有界队列:限制吞吐,队列填满问题
同步移交:没有线程等待且线程当前数量<最大数量,将创建线程,否则根据饱和策略,拒绝。当线程池无界or可以拒绝任务有效
任务相互独立,为线程池or工作队列设置界限才合理,如果任务之间存在依赖,有界线程池or队列可能导致饥饿死锁。推荐使用无界的线程池newCachedThreadPool
饱和策略
触发场景:有界队列填满后或者某个任务被提交到一个以被关闭的Executor时
ThreadPoolExecutor.setRejectedExecutionHandler设置
AbortPolicy(终止策略):默认策略,抛出RejectExecutionException
DisCardPolicy:新提交的任务无法保存到队列中等待执行时,抛弃该任务
DisCardOldestPolicy:抛弃下一个将被执行的任务,尝试重新提交新的任务(如果工作队列是一个优先队列,此策略会导致抛弃优先级最高的任务)
CallerRunsPolicy:将任务回退到调用者执行,主线程在一段时间内不能提交任务,使工作者线程有时间处理完正在执行的任务,在此期间主线程不会accept,到达的请求会保存在TCP层队列,持续过载,TCP会抛弃请求
路径为:线程池-工作队列-应用程序-TCP-客户端
ZA_BoundedExecutor
线程工厂
可设置线程池的配置信息,如执行UncaughtExceptionHandler或者更改线程优先级,给线程取名字等
如果需要利用安全策略控制对某些特殊代码库的访问权限,Executor.privilegedThreadFactory()定制,其创建的线程与创建privilegedThreadFactory的线程拥有相同权限
否则线程池将从需要新线程时调用executor/submit的客户程序中继承访问权限
ZB_MyAppThread
调用构造函数后再定制ThreadPoolExecutor
Executor通过Executors(newSingleThreadExecutor除外)工厂方法创建,可以强制转换ThreadPoolExecutor然后设置类似线程池基本大小的参数
设置unconfigurableExecutorService,可防止执行策略被修改
扩展ThreadPoolExecutor
ZC_TimingThreadPool
递归算法的并行化
ZD
案例:谜题框架
ZE

图形用户界面应用程序
可以在后台运行长时间任务,不会影响gui响应性,支持取消,完成通知以及进度通知的后台任务类、
ZF
分解模型设计:包含用于表示的数据模型,又包含应用程序特定的数据模型。如果一个数据模型必须被多个线程共享,而且无法实现一个线程安全的模型时,可以考虑用此设计
更新数据策略
1:相关信息快照嵌入到更新消息中
适用:数据模型小,更新频率不高
2:表现模式收到更新事件从共享模型中获得数据
发送增量更新信息,同时将共享模式中的更新操作序列化

---------------------------第三部分-活跃性,性能与测试-----------------------------
死锁:A_LeftRightDeadlock
动态死锁:D_DynamicOrderDeadlock
通过HashCode指定锁顺序解决死锁:C
开放调用:调用某个方法时不需要持有锁,避免死锁:F_Taxi
可通过线程转储信息分析死锁
资源死锁
线程饥饿死锁
你在等我的锁,我在等你的锁
其他活跃性危险
饥饿:线程无法访问它所需要的资源而不能继续执行
常见场景:线程优先级,持有锁执行无法结束操作
避免使用线程优先级,会增加平台依赖,并导致活跃问题
糟糕响应性:
优先前台响应性,而降低后台线程优先级
活锁(毒药消息):过度错误恢复代码,事务出错重复执行导致重复回滚
多个相互协作的线程都对彼此进行响应从而修改各自状态,导致任一线程无法继续执行。如2个有礼貌的人互相让路。
解决:重试机制引入随机性和回退

性能和可伸缩性
串行访问任务队列:G_WorkerThread
Amdahl定律:
F:必须被串行执行的部分
1-F:并行执行部分
N:N个处理器
最高加速比:speedup<=1/(F+(1-F)/N)
处理器利用率:speedup/N
要想保证吞吐量高,减少串行的百分比,因为并行可以依靠处理器分化掉

非竞争的同步会在jvm进行处理:常用优化策略
    锁清除:通过逸出分析找出不会发布到堆的本地对象引用(因此这个引用是线程本地的),消除锁
    锁粗粒度粗化:将邻近的同步代码块用同一个锁合并起来
竞争的同步:竞争锁失败的线程会阻塞
          阻塞行为有:自旋等待,通过循环不断尝试获取锁,适合等待时间较短场景
                    通过操作系统挂起被阻塞的线程,适合等待时间长
减少锁的竞争
    串行降低可伸缩性,切换上下文降低性能,锁发生竞争同时导致2种情况。
    3种方式
           1:减少锁持有时间
           2:降低锁请求频率
           3:使用带有协调机制的独占锁,这些机制允许更高的并发
减少锁持有时间:H_AttributeStore/I_BetterAttributeStore
            由于只有一个状态变量,可以使用其他容器类,如SyncMap/ConcurrentHashMap,这样无需显示同步,缩小了访问锁的范围,降低了代码维护风险
            缩小同步代码块不能影响需要原子的操作
            分解多个同步代码块时,可以将大量计算或阻塞操作从同步代码块中移除时,应该考虑
降低线程请求锁的频率:可采用如下2方法,其本质是采用多个相互独立的锁来保护独立的状态变量
            锁分解:J_ServerStatusBeforeSplit/K_ServerStatusAfterSplit
            锁分段:L_StripedMap
                    场景:锁上的竞争频率>锁保护的数据上发生的竞争频率
放弃独占锁:使用并发容器,读写锁,不可变对象以及原子变量
            ReadWriteLock:多个读取以及单个写入的规则,读不需要加锁,写需要加锁
            原子变量:降低热点域时的开销,如静态计数器,序列发生器,AtomicLong等
获取size方法
           1:每次调用统计一下元素数量O(N)
           2:插入和移除更新全局计数器0(1),降低扩展性
           3:将每个分段进行枚举并将每个分段中的元素数量相加
           4:在3基础上,缓存返回值,如果数量有修改,则返回值设置-1,如果非负,直接返回
检测CPU利用率,没有充分利用,通常原因如下:
            负载是否充足
            I/O密集
            外部限制
            锁竞争
对象池别用:对象分配操作的开销<同步的开销
使用消息队列,异步解决问题,能有效提升吞吐,上下文切换次数变少,管理也简单。

并发程序的测试
正确性测试
基于信号量的有界缓存:M
测试线程工具类:N
使用yield产生更多的交替操作,提高发现错误几率:O
性能测试
基于栅栏的定时器:P
除非线程由于密集的同步需求而被持续阻塞,否则非公平的信号量(不用栅栏)能更好吞吐量
避免性能测试陷阱
垃圾回收:
1:确保在测试期间垃圾回收不会执行,jvm设置
2:执行期间回收多次(推荐,更能反映实际情况下性能)
动态编译
不真实编译
如果应用程序在每次访问共享数据结构时执行大量的线程本地计算,可以极大降低竞争程度并提供更好性能。否则吞吐量主要受限于线程之间的协调开销
无用代码消除
其他测试
代码审查
静态分析工具
aop
分析与检测工具

--------------------第四部分-显示锁------------------------
通过tryLock避免顺序死锁:A_DeadlockAvoidance
可中断的锁获取操作:B_InterruptibleLocking
带有时间限制的加锁:C_TimedLocking
非块结构的加锁,连锁式加锁(锁耦合)
公平性:可通过ReentrantLock构造设置2种公平性
非公平(default):允许插队,当一个线程请求非公平锁时,如果在发出请求的同时该锁的状态变为可用,那么这个线程将跳过队列中所有等待线程并获得锁
公平:线程按照发出请求顺序来获取锁,如果有另一个线程持有这个锁or有其他线程在队列中等待这个锁,那么新发出的请求线程将被放入队列
持有锁的时间过长or请求锁的平均时间间隔较长,推荐公平锁。
持有锁or请求锁时间长会造成队列线程过多,如果在非公平情况下,过多线程同时唤醒去抢锁>公平情况下,队列第一个抢锁

读写锁:一个资源可以被多个读操作访问,或者被一个写操作访问,但2者不能同时进行:D_ReadWriteMap

存在状态依赖的类:类的一些操作中有着基于状态前提条件。如:不能从一个空队列中删除元素,获取一个尚未结束的任务的计算结果
E
条件队列:确保当某个条件成真时线程立刻醒来,队列元素是一个个正在等待相关条件的线程
丢失的信号:线程将等待一个已经发过的事件,但在开始等待之前没有检查条件谓词。
通知:
notify:随机选择一个唤醒,容易丢失信号
适用场景同时满足2点:所有等待线程的类型相同,只有一个条件谓词,每个线程从wait返回后执行相同操作
单进单出,每次通知,最多只能唤醒一个线程
notifyAll:通知全部
使用wait/notifyAll实现可重新关闭的阀门:F
AbstractQueuedSynchronizer:构建锁和同步器的框架,ReentrantLock/Semaphore/FutureTask等,基于它构建
使用显示条件变量的有界缓存:G
使用Lock实现信号量:H
使用AbstractQueuedSynchronizer实现的二元闭锁:I

原子变量与非阻塞同步机制
12原子变量类:标量类,更新器类,数组类,复合变量类
基于CAS实现的非阻塞计数器:J
通过CAS维持包含多个变量的不可变条件:K
锁与原子变量性能比:L
中低程度的竞争下,原子变量能提供更高的可伸缩性。高强度竞争下,锁更有效避免竞态.
锁在发生竞争时会挂起线程,降低CPU使用率和共享内存总线上的同步信号量,所以中低竞争程度尤其会更浪费资源
在高竞争下,原子变量会产生多余的竞争
能够避免使用共享状态,开销将会更小。用ThreadLocal能让每个线程只能看到自己私有的伪随机数

非阻塞算法:一个线程的失败或挂起不会导致其他线程也失败or挂起
无锁算法:算法的每个步骤中都存在某个线程能够执行下去
J_CasCounter:是非阻塞算法/无锁算法
非阻塞栈/非阻塞链接队列算法:M

ABA问题
    无法确保当前值没有发生变化,V由A变成B,在由B变成A。
    可以使用AtomicStampedReference支持在2个变量上执行原子的条件更新,在引用上加版本号"避免ABA问题"
    AtomicMarkableReference:更新一个对象引用-布尔值二元组,通过二元组使节点保存在链表中同时又将其标记为"已删除的节点"

java内存模型
偏序关系(Happens-before)
对于含有final域的对象,初始化安全性可以防止对对象的初始引用被重排序到构造过程之前

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值