多线程·锁池与等待池(jdk1.8);在乐观锁下,真的有必要关心CAS中的ABA问题吗???

本文主要是介绍多线程·锁池与等待池(jdk1.8);在乐观锁下,真的有必要关心CAS中的ABA问题吗???,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

进程、线程、协程

线程的目标

线程的切换

线程的管理

乐观锁策略


本来不想聊关于线程的话题,主要原因在于自己对计算机原理相关的知识了解太浅,怕耽搁大家。

可是网络上的文章太多东拼西凑、莫名其妙,最近在读一些博文的时候被气到了,于是勉强阐述一些个人在线程方面的理解。

 

进程、线程、协程

当说起线程的时候,总是避不开进程和协程,较官方些的定义如下:

进程:操作系统分配资源的基本单位,有独立的内存空间,通信效率低,切换开销大

线程:CPU调度的基本单位,共享父进程的资源,通信效率高,切换开销小

协程:数据安全,可由程序控制调度

 

在具体的使用过程中,进程相对于线程来说最主要的特点是安全,安全指的是线程存在于进程中,进程挂掉意味着所有的线程也一起销毁。比如nginx服务器,典型的多进程+多路复用,master进程负责接收请求、管理worker进程;worker进程处理请求,多路复用的目的是为了减少线程创建的开销;还有cache进程等。

协程相对于线程来说特点有两个,安全、效率。两个特点来自于协程的实现,协程使用同步实现异步,同步就意味着没有资源竞争,没有锁开销,没有切换开销;调度策略由开发者自行控制,可以回忆一下关于asm编程时的中断。

而在java中,使用最多的是线程,所以java程序员了解最多的也是线程。

 

线程的目标

首先必须要清楚使用线程的原因,最简单的,使用线程是为了提高程序性能。而线程解决的是IO密集型问题与CPU利用率问题。

IO密集指的是有大量的操作需要读/写,而此时的CPU处于等待状态,这种情况就是通常所说的BIO模式。

而我们作为一个残忍的剥削者,对于CPU老实的恶劣行径,是万万不能容忍的。这个时候就需要给他下达命令:“你先处理其他的事情吧,等会儿IO完成了再回来搞”,CPU于是把此时的IO操作先放在一边,屁颠屁颠地做其他的事情去了。

但是呢,CPU在处理其他事情的时候脑子里还记着刚才的IO操作,他可不能把这茬给忘了,所以这个做事的效率就差了一些,这个就叫存储上下文信息,即线程切换时的开销。

 

与之相对的就是计算密集,单CPU多线程不能解决计算密集问题,效果甚至比单线程更差。

比如今天你有两个计划:1.工作2.吃鸡

如果你花10s工作,再花10s吃鸡,如此反复,最终不光工作一塌糊涂,吃鸡估计也是吃不到了。

但是如果这个时候你的好哥们到你家来了,诶,现在相当于有两个CPU了。你认真工作,然后跟你哥们说:“你上我的号,帮我吃鸡吧,晚上请你吃饭”,这下就完美了,谁也不闲着,且都能专心做,这个就叫CPU充分利用。

 

线程的切换

线程的切换带来的另一个问题是共享资源的控制。

假设现在有一个餐厅,餐厅里有一张餐桌,厨师每做出一盘菜就端到餐桌上,但是呢,餐厅中没有灯,黑漆麻乌的啥也看不见。

在这里餐桌就是共享资源,餐桌上的菜就是资源的值,黑漆麻乌表示在操作资源前不知道资源的状态。

人们到了餐桌前徒手一扒拉,不论能否拿到餐盘,扒拉完一次就得走。如果有的人啥也没拿到,你说他下次还会光临吗?

后来啊,餐厅老板意识到了这个问题,于是在门口贴了一个二维码,每个客人就餐前需要扫码登记,登记完喊到谁的名字就让谁进,于是每出一盘菜老板就根据统计表里的人喊一嗓子,这个被喊到的人就能进入餐厅吃饭了,这个就是线程锁。

 

你觉得现在已经都搞定了,然而太天真了!

有的客人被翻到了牌子,进入餐厅正准备吃食,万事俱备之际,该客人突然闹肚子,遂狂奔茅厕。难道其他客人都等着他?不可能的,效率太低了!老板在该客人蹲坑的时候把他的名字记到小本本上,等他出来了,把他的名字再次登记一遍,这段时间不影响其他客人就餐,这个就叫线程的挂起与唤醒。

 

在java中,有两个概念,锁池与等待池(synchronized):

锁池:某个线程已经拥有了某个对象的锁,其他想要获取该对象的锁的线程就会进入该对象的锁池中

等待池:已经拥有某个对象的锁的线程调用了wait()方法,该线程将进入该对象的等待池中,等待池中的线程不会竞争该对象的锁

 

当执行该对象的notify()方法时,随机将等待池中的一个线程移到锁池中;当执行该对象的notifyAll()方法时,将等待池中的所有线程移动到锁池中。

锁池中的线程可以竞争该对象的锁。

 

wait()、notify()、notifyAll()实质上就是线程在锁池与等待池之间的移动,每个锁对象都有自己的锁池和等待池。

 

线程的管理

即便线程能够通过锁来控制合理的执行,但线程仍然不能够无限地创建,就算你不在乎线程切换的开销,但计算机的内存毕竟是有限的。

餐厅老板不可能让等待就餐的顾客从餐厅门口排队排到法国(麻烦法国的那位哥们帮我带瓶红酒回来,谢谢)。

 

线程池由此诞生,其最主要的作用我认为就是对于线程数量的控制:

1.降低资源消耗

2.提高响应速度

3.线程可管理性

 

在java中,常见的线程池有四种:

1. newCachedThreadPool()

2. newFixedThreadPool()

3. newScheduledThreadPool()

4. newSingleThreadExecutor()

四种线程池都是由ThreadPoolExecutor实现的,该类除了线程数以外,还有两个地方需要注意,一个是构造器参数中的taskQueue,如果定义的是一个无界队列,当线程处理速度小于task添加速度时,很容易造成内存泄漏;第二个是关闭线程池时的两个方法,shutdown()和shutdownNow(),相关资料请自行查阅。

 

乐观锁策略

上面所述都是基于synchronized关键字来讲的,可以看到synchronized是悲观锁,在每次操作数据前必须先拿到锁。

与悲观锁相对的是乐观锁,乐观锁就是在每次操作数据前都假设没有冲突,当执行过程中遇到了再做处理。

 

在java中,AutomicXx和ReentrantLock都是通过CAS实现的,CAS即CompareAndSwap,一种同步非阻塞的无锁算法,通过阅读源码,很容易理解。

 

 

具体过程有3个步骤:

1. 从内存中获取旧的值V

2. 通过旧的值V计算出新的值A

3. 将内存中的值B与V比较,如果相同,则将其修改成A,否则跳到第1步

 

CAS操作可以通过CPU的单条指令来完成,理论上来说效率是非常高的,但是在大量的线程频繁修改的情况下,单条线程长时间循环比较,此时CAS的表现就不尽人意了。

另外可以看到,CAS保证的是单个变量的原子性。

还有很多文章在聊AtomicXx的时候说到ABA问题,ABA问题简单来说就是一个值从A修改成B,再从B修改回A。而某些场景下,一个合理的操作不仅依赖于结果,同时依赖于过程。单独谈CAS的时候,提起ABA是没有问题的,可是在阐述AtomicXx等内容时,莫名其妙来这么一句,让人不知所以。我倒是觉得可以将CAS理解为一个最终一致性的乐观锁。

 

其实在ABA问题上,java也提供了支持,与AtomicReference相对的AtomicStampedReference,通过一个版本号来保证了执行顺序。

 

有任何问题或者发现文章中的错误之处,欢迎私信或者邮箱过来,谢谢。

 

公主号搜索:以镒称铢


这篇关于多线程·锁池与等待池(jdk1.8);在乐观锁下,真的有必要关心CAS中的ABA问题吗???的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/237423

相关文章

usb接口驱动异常问题常用解决方案

《usb接口驱动异常问题常用解决方案》当遇到USB接口驱动异常时,可以通过多种方法来解决,其中主要就包括重装USB控制器、禁用USB选择性暂停设置、更新或安装新的主板驱动等... usb接口驱动异常怎么办,USB接口驱动异常是常见问题,通常由驱动损坏、系统更新冲突、硬件故障或电源管理设置导致。以下是常用解决

Mysql如何解决死锁问题

《Mysql如何解决死锁问题》:本文主要介绍Mysql如何解决死锁问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录【一】mysql中锁分类和加锁情况【1】按锁的粒度分类全局锁表级锁行级锁【2】按锁的模式分类【二】加锁方式的影响因素【三】Mysql的死锁情况【1

SpringBoot内嵌Tomcat临时目录问题及解决

《SpringBoot内嵌Tomcat临时目录问题及解决》:本文主要介绍SpringBoot内嵌Tomcat临时目录问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录SprinjavascriptgBoot内嵌Tomcat临时目录问题1.背景2.方案3.代码中配置t

SpringBoot使用GZIP压缩反回数据问题

《SpringBoot使用GZIP压缩反回数据问题》:本文主要介绍SpringBoot使用GZIP压缩反回数据问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录SpringBoot使用GZIP压缩反回数据1、初识gzip2、gzip是什么,可以干什么?3、Spr

数据库面试必备之MySQL中的乐观锁与悲观锁

《数据库面试必备之MySQL中的乐观锁与悲观锁》:本文主要介绍数据库面试必备之MySQL中乐观锁与悲观锁的相关资料,乐观锁适用于读多写少的场景,通过版本号检查避免冲突,而悲观锁适用于写多读少且对数... 目录一、引言二、乐观锁(一)原理(二)应用场景(三)示例代码三、悲观锁(一)原理(二)应用场景(三)示例

如何解决idea的Module:‘:app‘platform‘android-32‘not found.问题

《如何解决idea的Module:‘:app‘platform‘android-32‘notfound.问题》:本文主要介绍如何解决idea的Module:‘:app‘platform‘andr... 目录idea的Module:‘:app‘pwww.chinasem.cnlatform‘android-32

kali linux 无法登录root的问题及解决方法

《kalilinux无法登录root的问题及解决方法》:本文主要介绍kalilinux无法登录root的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,... 目录kali linux 无法登录root1、问题描述1.1、本地登录root1.2、ssh远程登录root2、

SpringBoot应用中出现的Full GC问题的场景与解决

《SpringBoot应用中出现的FullGC问题的场景与解决》这篇文章主要为大家详细介绍了SpringBoot应用中出现的FullGC问题的场景与解决方法,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录Full GC的原理与触发条件原理触发条件对Spring Boot应用的影响示例代码优化建议结论F

MySQL 中查询 VARCHAR 类型 JSON 数据的问题记录

《MySQL中查询VARCHAR类型JSON数据的问题记录》在数据库设计中,有时我们会将JSON数据存储在VARCHAR或TEXT类型字段中,本文将详细介绍如何在MySQL中有效查询存储为V... 目录一、问题背景二、mysql jsON 函数2.1 常用 JSON 函数三、查询示例3.1 基本查询3.2

Pyserial设置缓冲区大小失败的问题解决

《Pyserial设置缓冲区大小失败的问题解决》本文主要介绍了Pyserial设置缓冲区大小失败的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录问题描述原因分析解决方案问题描述使用set_buffer_size()设置缓冲区大小后,buf