JAVA并发(一)
java并发的一系列框架和技术主要是由java.util.concurrent 包所提供。包下的所有类可以分为如下几大类:
- locks部分:显式锁(互斥锁和速写锁)相关;
- atomic部分:原子变量类相关,是构建非阻塞算法的基础;
- executor部分:线程池相关;
- collections部分:并发容器相关;
- tools部分:同步工具相关,如信号量、闭锁、栅栏等功能
整体实现技术可按照依赖级别分为以下三层:
|
高层类 |
Lock 同步工具 并发容器 Executor/ExecutorCompletionService |
|
基础类 |
AQS 非阻塞数据结构 原子变量类 |
|
底层原理 |
volatile变量的读/写 CAS |
一、volatile
保证线程之间操作的可见性,避免操作的重排序,但不保证原子性(i++)。
由于 Java 内存模型(JMM)规定,所有的变量都存放在主内存中,而每个线程都有着自己的工作内存(高速缓存)。
在当前的Java内存模型下,线程可以把变量保存在本地内存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。
Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值;当成员变量发生变化时,强迫线程将变化值回写到共享内存。由于使用 volatile 屏蔽掉了 VM 中必要的代码优化,所以在效率上会低效一些。
二、CAS无锁算法
CAS(comapre and swap):CAS是一项乐观锁技术,其核心思想为冲突检测和数据更新。Java对于CAS支持是利用sun.misc.Unsafe这个类通过JNI调用CPU底层指令实现Unsafe.compareAndSwapInt操作。其主要实现思路为:将内存值V与旧的预期值E,如果相同则更新为U,不相同则由上层系统循环获取实际值后,再次调用此CAS算法。最主要的应用就在原子变量类的具体实现中。
|
将内存值V与旧的预期值E,如果相同则更新为U,不相同,返回内存值给用户。
|
|
但CAS存在问题:ABA问题/循环时间长开销大/只能保证一个共享变量的原子操作 ABA:如栈结构(A-B),线程T1要移除A,B变栈顶,,线程T2将结构变为A-C-D,,此时T1进行CAS冲突检测发现A没变,但实际上整个栈结构变了,此时进行操作会覆盖掉C-D,解决办法是使用原子类AtomicStampedReference来保证整个栈的一致性。 对于线程冲突较轻,使用CAS能够避免加锁和释放锁的操作,消耗CPU资源。 对于线程冲突较重,CAS容易产生自旋,即不停比较然后失败重试,浪费CPU。 |
|
三、AQS及其他同步工具
AbstractQueuedSynchronizer抽象队列同步器,定义了多线程访问资源的同步框架。用于构建锁和其他同步组件CountDownLatch等的基本框架。使用一个volatile(代表共享资源)来维护状态,通过内置的FIFO队列来完成资源获取线程的排队工作。如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列(带头节点的双向非循环链表)锁实现的,即将暂时获取不到锁的线程加入到队列中。
AQS两类应用场景:Exclusive(资源独占,只有一个线程能执行,如ReentrantLock)和Share(资源共享,多个线程可同时执行,如Semaphore/CountDownLatch)
1.CountDownLatch: 倒计时器,同步工具类,允许一个或多个线程,等待其他一组线程完成操作,再继续执行。在CountDownLatch 上定义两种操作:CountDown.countDown表示该线程工作结束(计数器减一)、CountDown.await当前线程阻塞,等待其他工作线程结束(计数器为0)
2.CyclicBarrier:线程屏障,同步工具类,允许一组线程相互之间等待,达到一个共同点,再继续执行。能够重置并多次使用,并且能够获取阻塞线程数量;不会阻塞主线程。
3.Semaphore:信号量,用于控制线程的并发数量。在信号量上我们定义两种操作: acquire(获取) 和 release(释放)。当一个线程调用acquire操作时,它要么通过成功获取许可(许可减1),要么一直等下去,直到有线程释放许可,或超时。release(释放)实际上会将许可的值加1,然后唤醒等待的线程。
使用Seamphore(2),创建了多少线程(5),实际就会有多少线程执行(5),只是可同时执行的线程数量会受到限制(2)。但使用线程池(2,5),不管你创建多少线程实际可执行的线程数是一定的(2)。
4.Exchanger:交换者,实现线程间的相互数据交换或通信。提供一个同步点,当两个线程都达到该同步点时,则进行交换数据,可以多个进行随机交换,但必须为偶数个。无锁,通过循环 cas 来实现线程安全。eg:String data2 = (String) exchanger.exchange(data1)。
四、非阻塞数据结构
同步集合:Vector、HashTable,同步集合包装类/Collections.synchronizedMap()和Collections.synchronizedList()---同步集合会对整个May或List加锁。
ConcurrentHashMap:HashMap的并发级别,通过继承自ReentrantLock的Segment来对Hahs表进行分段锁,提高了并发效率
ConcurrentQueue也是通过同样的方式来提高并发性能的,子类ConcurrentLinkedQueue。例子:多线程卖票。
CopyOnWriteArrayList:写时复制容器,复制该容器进行写操作,将当前容器进行Copy,复制出一个新的容器,然后向新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为在当前读的容器中不会添加任何元素。所以CopyOnWrite容器是一种读写分离的思想,读和写对应不同的容器。
BlockingQueue:阻塞队列,并发容器,没有元素时-读取会堵塞,元素满时-加元素会阻塞。适合消费者生产者模式,其中ExecutorCompletionService就是将LinkedBlockingQueue和Executor结合管理线程返回结果。
|
ArrayBlockingQueue |
有界队列,使用一个ReentrantLock 锁。 |
|
LinkedBlockingQueue |
无界队列,内部使用ReentrantLock实现插入锁(putLock)和取出锁(takeLock),fixedThreadPool用的这个。 |
|
DelayQueue |
其中的对象只能在其到期时才能从队列中取走。这种队列是有序的,即队头对象的延迟到期时间最长。但是要注意的是,不能将null元素放置到这种队列中.eg:处理超时的客户端链接、超时的缓存对象 |
JAVA并发(一)的更多相关文章
- 多线程的通信和同步(Java并发编程的艺术--笔记)
1. 线程间的通信机制 线程之间通信机制有两种: 共享内存.消息传递. 2. Java并发 Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式执行,通信的过程对于程序员来说是完全透 ...
- 【Java并发编程实战】----- AQS(四):CLH同步队列
在[Java并发编程实战]-–"J.U.C":CLH队列锁提过,AQS里面的CLH队列是CLH同步锁的一种变形.其主要从两方面进行了改造:节点的结构与节点等待机制.在结构上引入了头 ...
- 【Java并发编程实战】----- AQS(三):阻塞、唤醒:LockSupport
在上篇博客([Java并发编程实战]----- AQS(二):获取锁.释放锁)中提到,当一个线程加入到CLH队列中时,如果不是头节点是需要判断该节点是否需要挂起:在释放锁后,需要唤醒该线程的继任节点 ...
- 【Java并发编程实战】----- AQS(二):获取锁、释放锁
上篇博客稍微介绍了一下AQS,下面我们来关注下AQS的所获取和锁释放. AQS锁获取 AQS包含如下几个方法: acquire(int arg):以独占模式获取对象,忽略中断. acquireInte ...
- 【Java并发编程实战】-----“J.U.C”:CLH队列锁
在前面介绍的几篇博客中总是提到CLH队列,在AQS中CLH队列是维护一组线程的严格按照FIFO的队列.他能够确保无饥饿,严格的先来先服务的公平性.下图是CLH队列节点的示意图: 在CLH队列的节点QN ...
- 【Java并发编程实战】-----“J.U.C”:CountDownlatch
上篇博文([Java并发编程实战]-----"J.U.C":CyclicBarrier)LZ介绍了CyclicBarrier.CyclicBarrier所描述的是"允许一 ...
- 【Java并发编程实战】-----“J.U.C”:CyclicBarrier
在上篇博客([Java并发编程实战]-----"J.U.C":Semaphore)中,LZ介绍了Semaphore,下面LZ介绍CyclicBarrier.在JDK API中是这么 ...
- 【Java并发编程实战】-----“J.U.C”:ReentrantReadWriteLock
ReentrantLock实现了标准的互斥操作,也就是说在某一时刻只有有一个线程持有锁.ReentrantLock采用这种独占的保守锁直接,在一定程度上减低了吞吐量.在这种情况下任何的"读/ ...
- Java并发编程:volatile关键字解析
Java并发编程:volatile关键字解析 volatile这个关键字可能很多朋友都听说过,或许也都用过.在Java 5之前,它是一个备受争议的关键字,因为在程序中使用它往往会导致出人意料的结果.在 ...
- JAVA并发编程J.U.C学习总结
前言 学习了一段时间J.U.C,打算做个小结,个人感觉总结还是非常重要,要不然总感觉知识点零零散散的. 有错误也欢迎指正,大家共同进步: 另外,转载请注明链接,写篇文章不容易啊,http://www. ...
随机推荐
- BZOJ1266 [AHOI2006]上学路线
Description 可可和卡卡家住合肥市的东郊,每天上学他们都要转车多次才能到达市区西端的学校.直到有一天他们两人参加了学校的信息学奥林匹克竞赛小组才发现每天上学的乘车路线不一定是最优的. 可可: ...
- JS算法之A*(A星)寻路算法
今天写一个连连看的游戏的时候,接触到了一些寻路算法,我就大概讲讲其中的A*算法. 这个是我学习后的一点个人理解,有错误欢迎各位看官指正. 寻路模式主要有三种:广度游戏搜索.深度优先搜索和启发式搜索. ...
- laravel之路由汇总
- vue2.0 学习 ,开始学习
先看官网的介绍上面的教程 https://cn.vuejs.org/v2/guide/ 尝试 Vue.js 最简单的方法是使用 JSFiddle Hello World 例子.你可以在浏览器新标签 ...
- [算法练习]Add Two Numbers
题目说明: You are given two linked lists representing two non-negative numbers. The digits are stored in ...
- XML与web开发-01- 在页面显示和 XML DOM 解析
前言: 关于 xml 特点和基础知识,可以菜鸟教程进行学习:http://www.runoob.com/xml/xml-tutorial.html 本系列笔记,主要介绍 xml 在 web 开发时需要 ...
- GPG error: http://extras.ubuntu.com trusty Release: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY F60F4B3D7FA2AF80
今天在更新运行apt-get update的时候出现了如下的错误: W: GPG error: http://extras.ubuntu.com trusty Release: The followi ...
- CPU纯软件全虚拟化技术
我们在前面的文章中提到了虚拟化技术的大致分类情况,即分为全虚拟化.半虚拟化和硬件辅助虚拟化3大类.而我们虚拟化技术最主要的虚拟主体就是我们的硬件CPU.内存和IO,那么我们的CPU在全虚拟化模式下如何 ...
- GitHub初步探索-1-使用本地代码管理工具,简化上传的过程
使用GitHub对于我们写Java的同志们来说是一个非常好的代码存储的方式,但是因为是全英文的,操作起来有一点复杂,所以我不是经常使用 ,但是最近代码越敲越多,再加上老师要求,希望使用比较简单的方法来 ...
- ajax实现跨域请求
因为现在一直用的mvc,所以就以mvc来说说ajax跨域提交. 首先说说跨域,简单说就是不同域名访问,比如在aaa.com访问bbb.com. 就拿招聘网站来说,分为两种用户,求职者和企业,求职者端是 ...