多线程并发22进阶
书:java编程思想
![]()
![]()
![]()
![]()
![]()
java并发编程实战
EA(企业应用架构模式)
1.多线程设计模式:Future模式(netty)、Master-Worker模式(Storm)、生产者-消费者(mq)
2.线程安全:当多个线程访问一个类(对象或方法),此类始终能表现出正确的行为
多个线程多个锁:多个线程,每个线程都可拿到自己制定的锁,分别获得锁之后,执行synchronized方法体的内容
synchronized取得的锁都是对象锁,而不是把一段代码(方法)当做锁
当静态方法上加synchronized表示锁定.class类(类级别的锁,独占.class类)安全性更优
3.对象锁的同步和异步:
同步(共享):synchronized,如果没有共享,就没必要同步
:原子性(同步)
:可见性
异步:asynchronized 独立,相互之间不受到任何制约‘
分表:
分库:减缓数据库连接上限压力
4.脏读:读取到非正确修改后的数据
在对一个对象的方法加锁的时候,需要考虑业务的整体性,建议对(setV/getV)同时加锁synchronized,保证业务的原子性(侧面保证业务一致性)
,(牺牲部分性能,建议减小锁的粒度)
orcale:undo保证数据 强一致性
A:原子性
C:一致性
I:隔离性
D:持久性
5.锁重入:当一个线程得到了一个对象的锁后,再次请求此对象时时可以再次得到该对象的锁(加锁方法内部可再调用加锁的方法)
Lock:是一个接口,提供了无条件、可轮询、定时的、可中断的锁获取操作,所有加锁和解锁的方法都是显式的。
核心方法:lock() 获取锁,如果锁不可用,将禁用当前线程,并在获得锁之前,该线程一直出于休眠状态
ReentrantLock:互斥的同步器,具有扩展能力。吞吐量通常比synchronized好
Lock与synchronized比较:
1.Lock使用起来比较灵活,但是必须有释放锁的动作
2.Lock必须手动释放锁和开启锁,synchronized不需要
3.Lock只适用于代码块锁,而synchronized对象之间的互斥关系
常见锁参考:https://www.cnblogs.com/lxmyhappy/p/7380073.html
https://yq.aliyun.com/articles/34759
分段锁:系统提供一定数量的原始锁(借鉴concurrentHashMap的分段思想),根据传入对象的哈希值获取对应的锁并加锁;
注意:锁的对象的哈希值如果发生改变,有可能导致所无法成功释放
6.volatile:作用是使变量在多个线程间可见(具有可见性,不具备原子性(使用atomic)和同步性,可当做轻量级synchronized,性能要比synchronized强很多,
不会造成阻塞,netty中大量使用,volatile只针对于多个线程可见的变量操作,不能代替synchronized的同步功能);当变量改变时强制线程执行引擎去
主内存里读取在java中每个线程都会有一块工作内存,其存放着所有线程共享的主内存的变量值的拷贝。
当线程执行时,会在自己的工作内存中操作变量,为了存取一个共享的变量,一个线程通常先获取
锁定并清除它的内存工作区,把共享变量从共享内存区中正确地装入到其所在的工作内存中,当线程解锁时
保证该工作内存区中变量的值写回到共享内存中
static不能保证原子性,atomic类能保证自身是原子性,无法保证整个方法是原子性的
字符串加锁比较特殊,字符串内容改变了,则锁字符串将无效
synchronized:对象、ObjecA.class均可加锁
7.线程通信:线程是操作系统中的个体,通信提高cpu利用率和对线程任务在处理过程中的把控与监督
a.wait和notify必须配合synchronized使用
b.wait方法释放锁,notify方法不释放锁
CountDownLatch:当需要等到某一条件,发送信号量用(有延迟) //等待某个事件的发生
await(类似wait)和countDown(类似notify)也能实现通信
CyclicBarrier:等待所有的线程一起完成后再执行某个动作,即一组线程中只要一个未完成,则已完成的调用await等待其他线程执行完
8.BlockingQueue(阻塞队列):支持阻塞机制的队列,阻塞的放入和得到数据 生产者和消费者模型用到
put:把对象加到队列,如果BlockQueue没有空间,则调用此方法的线程会被阻塞,
直到BlockingQueue里面有空间再继续
take:取走BlockingQueue里排在首位的对象,如为空,进入阻塞等待状态,直到有新的
数据被加入。
9.ThreadLocal:当前线程局部变量(与其他线程隔离),在高并发情况下,可减少锁竞争
10.多线程下的单例,随类初始化性能好:静态内部类

11.同步类容器(Vector、HashTable由Collections.synchronized*方法进行加锁,jdk1.5之前):串行化执行性能差、线程安全,但在某些场景下
可能需要加锁来处理复合操作(迭代等)
最常见异常:ConcurrentModificationException原因是当容器迭代的过程中,被并发的修改了内容,由于早期迭代器未考虑并发的问题
java.util.concurrent包完全建立在CAS之上的,没有CAS就没有并发包。并发包借助了CAS无锁算法实现了区别于synchronized同步锁的乐观锁。
因为对于CAS算法来说,就是在不加锁的前提下而假设没有冲突去完成某个操作,如果因为冲突而导致操作失败,那么就进行重试,直到成功为止。
CAS有三个操作数:真实的内存值V、预期的内存值A、要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为新值B,否则什么都不做。
缺点:虽然CAS有效的解决了原子操作的问题,但是其仍然有三个劣势:
1、ABA问题:因为CAS需要在操作前检查下值有没有发生变化,如果没有则更新。但是如果一个值开始的时候是A,变成了B,又变成了A,
那么使用CAS进行检查的时候会发现它的值没有发生变化,但是事实却不是如此。
ABA问题的解决思路是使用版本号,如A-B-A变成1A-2B-3A
2、循环时间长开销大:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。
3、只能保证一个共享变量的原子操作:对一个共享变成可以使用CAS进行原子操作,但是多个共享变量的原子操作就无法使用CAS,这个时候只能使用锁。
12.并发类容器(jdk1.5之后):ConcurrentHashMap代替HashTable
ConcurrentMap:
ConcurrentHashMap:内部使用段(Segment)来表示不同的部分(相当于小的HashTable),只要多个修改操作发生在不同的段上,就可以并发进行,
把一个整体分成了16个段,即最大支持16个线程的并发修改操作(多线程场景时减少锁的粒度,从而降低锁竞争的一种方案),并且代码中多使用
volatile关键字,目的是第一时间获取修改的内容,性能好
map.putIfAbsent()//如果key存在,就不执行put操作(cm的新增方法)
ConcurrentSkipListMap(支持并发排序)
13.CopyOnWrite:写时复制的容器,通俗理解:往一个容器添加元素,不直接添加,而是先将当前容器进行Copy,复制出一个新的容器,添加完元素之后,再将原容器的引用指向新的容器(读多写少操作时使用) 能否按小段进行copy?
好处:可对CopyOnWrite容器进行并发的读,而不需要加锁(读写分离的思想)
CopyOnwriteArrayList:
CopyOnWriteArraySet:
14.Queue:
ConcurrentLinkedQueue:高性能的队列代表
适用于高并发场景,通过无锁方式,实现了高并发状态下的高性能,基于链接节点的无界线程安全队列,队列中不允许null元素
add()和offer():加入元素在ConcurrentLinkedQueue中无区别
poll()和peek():取头元素节点,前者会删除元素,后者不会
size会遍历整个集合,速度慢,尽量避免,判断队列是否为空最好用isEmpty()
BlockingQueue:阻塞队列代表
add():将指定的元素插入到队列(如果可立即执行且不违反容量限制),成功是返回true,没有可用空间抛出IllegalStateException
offer():将指定的元素插入到队列(如果可立即执行且不违反容量限制),队列满时返回false,有容量限制的队列时,此方法优于add
put():将指定元素插入到队列,如有必要,则等待空间变得可用
poll:队列为空,返回null
remove:队列为空,抛出NoSuchElementException异常
take:队列为空,发生阻塞,等待有元素

13.多线程的设计模式,Future模式: 类似商品订单,Ajax请求,用户无需一直等待请求的结果

netty:FutureChannel采用此模式,readAndFlush()冲刷到缓冲区的过程是异步的;JDK的FutureTask
14.MasterWorker模式:并行计算模式,核心是系统由两类进程协作工作:Master进程和Worker进程,
Master负责接收和分配任务,Worker处理子任务。当各个Worker子进程处理完成后,会将结果返回
给Master,由Master做归纳和总结,好处是将一个大任务分解成若干个小任务,并行执行,从而提高
系统的吞吐量。分布式、大数据,hadoop的namenode和datanode

15.生产者和消费者:通常由两类线程(生产者和消费者线程),生产者负责提交用户请求,
消费者线程则负责处理生产者提交的任务,两者通过共享内存缓存区进行通信。MQ:底层多为netty
采用阻塞队列

16.jdk多任务执行框架:Executor

Executors是一个静态工厂,创建线程池方法(ThreadPoolExecutor实例化线程池):内部采用适配器模式?
newFixedThreadPool():返回固定数量的线程池,该方法的线程数始终不变,当有任务提交,如果线程池中空闲,则立即执行,
若没有则会被暂缓在一个任务队列中等待有空闲的线程再执行

newSingleThreadExecutor ():创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中

newCachedThreadPool():可根据实际情况调整线程个数的线程池,不限制最大线程数量,若有任务则创建线程,
若无任务则不创建线程。如果无任务则线程在60s后自动回收(默认)

newScheduledThreadPool():返回一个SchededExecutorService对象,但线程池可指定线程的数量。

ScheduledExecutorService和TimerTask对比:http://blog.csdn.net/guozebo/article/details/51090612
线程池的shotdown方法,jdk底层采用shutdown hock机制,友好地关闭线程
17.自定义线程池:当Executors工厂类无法满足业务需求,可自己创建自定义的线程池,使用有界队列和无界队列参数差异较大:
有界队列:有新任务执行,线程池实际线程数小于corePoolSize,则优先创建线程,
若大于corePoolSize,则将任务加入队列;若队列满了,则在总线程数不大于maxPoolSize的前提
下创建线程;若线程数大于maxPoolSize,则执行拒绝策略,或其他自定义方式。
无界队列:LinkedBlockingQueue,与有界队列比,除非系统资源耗尽,否则无界的任务队列不存在任务入队
失败的情况。当有新任务到来,系统线程数小于corePoolSize,则新建线程执行任务,当达到corePoolSize 后,则任务直接进入队列等待,不继续增加。若仍有新任务加入,
JDK拒绝策略:自定义拒绝策略实现RejectedExecutionHandler接口,重写rejectedExecution()
AbortPolicy:直接抛出异常阻止系统正常工作
CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前丢弃的任务
DiscardOledestPolicy:丢弃最老的一个请求,尝试再次提交当前任务。
DiscardPolicy:丢弃无法处理的任务,不在做任何处理

20.disruptor:特别适用于对时间高度敏感的多线程应用,实现了有界队列的功能,对时间不敏感用ArrayBlockingQueue即可
原理:a.RingBuffer复用内存,减少分配新空间带来的时空损耗
b.单生产者对N消费者(不用锁)
c.BusySpin(疯狂死循环)是多核架构上最快的通讯方法,比经kernel走信号量都快

posted on 2018-03-06 18:17 xiaojiayu0011 阅读(344) 评论(0) 收藏 举报
浙公网安备 33010602011771号