@lxx3061313
2018-10-09T13:01:13.000000Z
字数 8645
阅读 127
并发
解决线程安全的几种思路:1.悲观锁2.cas 3.写时复制。
线程创建方式
:1.继承Thread,2.实现Runnale.
基本属性
:id,name,优先级,状态,daemon标记,sleep方法(可被中断),yield方法(只是建议),join方法(等待其他方法结束).
状态
:NEW(没有调用start方法的线程),TERMINATED(线程运行结束后),RUNNABLE(start后,线程可以执行或者正在执行),BLOCKED(一般等待锁),WAITING(wait/join/sleep方法),TIME_WAITING(带时间的wait/join/sleep方法)。
共享内存存在的问题
:1.竞态条件,多个线程方位和操作同一个对象,最终结果与执行时序有关。解决方案:synchronized, lock, 原子变量。2.内存可见性,一个线程对一个共享变量的修改,另一个线程可能不能及时看到。原因:线程有私有内存和共有内存。方案:volatile,synchronized, lock
用法
:实例方法,静态方法,代码块。原理:对象监视器,对象锁。
高级特性
: 可重入性,内存可见性,可能造成死锁,解决方案是尽量不要在获得一个锁的时候获取另一个锁,如果必须要,则相同的顺来来获得。也可以换成lock,lock有trylock,lock(time)。
同步容器
: 每个方法上加synchronized。缺点:解决不了复合操作问题,有迭代问题。
并发容器
:CopyOnWriteArrayList,ConcurrentHashMap,ConcurrentLinkQueue,ConcurrentSkipListSet. 没有使用synchronize,没有迭代问题。
锁优化
:偏向锁,轻量级锁,重量级锁。
中断
:等待synchronized锁的过程中,线程不会响应中断,这是synchronize的局限性。
局限性
:1,不能响应中断;2,不能非阻塞方式获取锁,3,不能限时,4唤醒不公平
场景
:生产者消费者,同时开始,等待结束,异步结果,集合点。
wait/notify
:wait可以被中断,主要语义是等待一个条件,这个条件需要其他的线程满足。每个对象有两个队列,锁等待队列,条件等待队列。等待synchronize的进入锁等待队列,调用wait的进入条件等待队列。notify/notifyAll会唤醒条件等待队列的线程。注意点:wait/notify必须在synchronize代码块内使用。调用wait方法后,该线程会释放对象锁。
wait过程
:1. 当前线程进入条件等待队列,释放锁,阻塞等待,线程状态变为WAITING或者TIME_WAITING,2.等待时间到,或者有其他线程调用notify/notifyAll, 这时需要重新竞争锁,竞争成功则从wait方法返回,线程状态变为RUNNABLE,竞争失败则线程状态变为BLOCKED,进入锁等待队列。当从wait返回后,条件不一定满足,需要重新判断。
notify
:唤醒条件队列中的线程,但是不释放锁,包含在synchronize内的代码执行完才释放。
notify/wait总结
:wait等待的是共享条件,notify在负责在条件满足后唤醒。wait和notify被不同的线程调用,但是共享相同的锁和条件等待队列,他们围绕一个通向的条件变量进行写作。只能有一个条件等待队列,这是notify/all的局限性,这在生产者消费者模式总带来了一些不便。
生产者消费者
:他们的协作变量是一个队列。所以核心点是基于wait/notifyAll实现一个阻塞队列。
同时开始
:协作的变量是一个标记,标记封装了wait和notify操作。
等待结束
: 共享变量是一个值,初值等于子线程个数。主线程等这个值变为0,子线程对值减1.参考java的CountDownLatch. 当然如果初值设置为1,也可以用到上面同时开始的场景。
异步调用
:future是一个重要的概念,是实现任务的执行和任务的提交相分离的关键,是其中的纽带。任务提交者和任务执行服务通过它隔离了各自的关注点,同时进行协作。
future和FutureTask
:
集合点
:协作变量依然是一个数,这个数表示未到集合点的线程个数。
取消关闭线程
:场景1,一个线程无线循环的执行任务,需要关闭。场景2,一个下载任务,下载了一半想要取消。场景3, 超时任务未结束,想要取消。场景4,多线程只要一个线程成功,其他失败取消。
如果关闭一个线程
:停止一个线程的主要机制是中断,中断不是强迫终止一个线程,它是一种协作机制,给线程传递一个取消信号,但是有线程自己来觉得何时以及如何退出。
终端标志位
:每个线程都有一个中断标志位,表示该线程是否被中断了。
常用方法
:isInterrupted(),实例方法,返回对应的线程的中断标志位是否为true,interrupted(),类方法,会通过Thread.currentThread操作当前线程,返回当前线程的中断标志位是否为true,另外该方法有一个副作用就是在返回标志位后会清空标志位。interrupt()实例方法,中断对应的线程。
interrupt()方法
:interrupt对线程的影响与线程的状态有关。RUNNABLE,只会设置线程的中断标志位,不会有其他作用,线程可以在运行过程中自己检测该标志位,决定是否相应。WAITING/TIME_WAITING,线程调用wait/join/sleep会进入这个状态,调用interrupt()会直接抛出InterruptedException异常抛出异常后会清理中断标志位。两种处理方式:1.向上继续抛,表示当前方法可以被终端。2.不可以抛(比如Thread.run方法),则做一些清理动作,重新调用interrupt()设置终端标志位。BLOCKED, 如果是等待synchronize进入的BOLCKED状态,只会设置中断标志位。NEW/TERMINATED,不会有任何作用,标志位都不会设置。
原子变量
:基于CAS,内部变量使用volatile修饰。
ABA问题
:使用一个AtomicStampedReference,在原有基础上加一个时间戳(版本号).
ReentrantLock
: 可重入,可以解决竞态问题,可以解决内存可见性,支持公平获取锁。
公平性理解
:公平性影响性能,不是因为计算线程等待时长耗时,而是因为活跃线程频繁调用LockSupport.part()导致线程挂起,引起上下文切换。
ReentrantLock
: 依赖CAS, LockSupport.
LockSupport
:LockSupport.park()使当前线程进程WAITING状态,可响应中断。LockSupport.unpark(Thread t)使t恢复运行。LockSupport内部调用了UnSafe的方法,底层是native方法。
AQS
:AbstactQueuedSynchronized,
Synchronize&ReentrantLock
: Synchronized代表了一种声明式思维,ReentrantLock代表了一种编程式思维。ReentrantLock可以以非阻塞方式获得锁,可以响应中断,可以限时。
含义
:锁解决竞态问题,条件变量解决协作问题。lock与Synchronize对应,condition与wait/notify对应。wait/notify与synchronize配合,条件跟lock配合使用。可以创建多个条件队列,wait/notify只能创建一个等待队列。
await
: await大部分方式是响应中断的,但是也有不响应中断的方法。
过程
:await在进入等待队列后,会释放锁,释放CPU,当其他线程将它唤醒后,或者等待超时后,或被中断后,需要重新获取锁,获取失败进入BLOCKED状态,获取成功从await返回。另外,从await返回后不代表其等待的条件一定满足了,需要重新判断(通常放在一个循环内部)。
使用
:调用await/signal前需要获取lock
重新写blockingQueue
:
实现原理
:Condition在AQS内部有对应了一个内部类ConditionObject.ConditionObject内部有一个队列,表示的是条件等待队列。分写一下await()方法,1.创建对了节点放入ConditionObject的等待队列,2.释放持有的锁和CPU(Locksupport.park()实现)3.被终端,或者被移出等待队列后,重新获取锁,获取锁后从await返回。 分析一下signal:1.将线程移出等待队列,2.调用LockSupprot.unpark().
特点
:线程安全,支持原子复合操作,迭代不需要加锁。读不加锁,可以并发读,读写也可以并行,写写不行。每个写操作需要获取锁。
原理
:内部是一个数组,用volatile修饰。每次修改都会复制一个数组,复制原数组的内容到新的数组,然后以原子方式设置内部的数据引用。这就是写时复制。所有的读操作,都是先当前数组的引用,然后直接访问数据,即时访问过程中,数组发生了变化,也不影响读操作,它依旧读取的是旧内容。内部通过ReentrantLock解决同步问题。
场景
:数组小,没有频繁修改的场景。读不需要锁,因此牺牲了写性能。
特点
:并发安全,支持原子符合操作,支持高并发,读完全并行,写一定程度并行,迭代不加锁,弱一致性。HashMap的并发版本,HashMap在多线程写的情况下会出现死锁,死锁出现的情况是多个线程并发扩容的时候。
原理
:
特点
:不想TreeMap/TreeSet,并发版本基于跳表实现。无锁,非阻塞,完全并行。
弱一致性,可以迭代修改。
无锁非阻塞并发队列
:ConcurrentLinkedQueue/ConcurrentLinkedDeque
不用锁,使用cas
普通阻塞队列
:ArrayBlockingQueue/LinkedBlockingQueue/LinkedBlockingDeque 依赖锁和条件
优先级队列
:PriorityBlockingQueue
延时队列
:DelayQueue
其他
:SynchronizedQueue/LinkedTransferQueuei
异步任务提出了执行服务的概念,将任务的提交和任务的执行相分离。
涉及接口
:Callable,Runnable表示要执行的任务。Executor,ExecutorSevice表示执行服务,ExecutorService扩展了Executor,定义了更多的服务。Future,异步结果,可以获取任务状态,可以取消任务(中断不一定能取消),获取任务结果,支持限时。
Future
:Future是一个重要的概念,是实现任务的提交和任务的执行相分离的关键,是其中的纽带。任务提交者和任务执行者通过它分离各自的关注点,同时进行协作。
原理
: 调用submit方法后,会将Callable包装成一个RunnableFuture,RunnableFuture是一个接口,FutureTask是其实现类。作为Runnable,RunnableFuture表示要执行的任务,作为Future,RunnableFuture表示异步结果。
FutureTask
:异步任务的核心是FutureTask, 它既表示执行的任务,又表示任务的结果,是关注点分离的纽带。所以在FutureTask内部,有一个变量callable用来引用需要包装的任务,有一个变量state表示任务执行的状态,后续的get方法,cancel都会基于这个状态完成功能。有个变量outcome表示结果或者异常,有一个runner表示执行中的线程,有一个队列表示等待任务的结果线程。
FutureTask.run()
:FutureTask表示一个要执行的任务,所以内部封装了任务的具体执行,通过run方法来做。run方法内部设置了结果,状态,异常等结果信息。基本逻辑为:1.调用callable.call()方法,捕获任何异常。2.如果正常执行,设置outcome结果。3.如果发生异常,设置outcome异常结果。4.最后调用finishComplete(),唤醒等待的线程(通过LockSupport.unpart())。
FutureTask.get()
:get()方法是任务提交者来使用的,用来获取任务结果,可以支持限时等待。基本逻辑为:1.如果任务还未执行完毕,就等待,2.如果完毕,调用report报告结果,report根据状态返回结果或者异常。
FutureTask.cancel()
:本质上通过中断来尝试结束线程。基本逻辑为:1.如果任务已经结束或者取消,返回2.如果参数允许中断,则调用interrupt设置中断。3.如果不允许中断,直接设置状态为CANCELED。4.finishComplete()唤醒线程。
概念
: 线程池有两部分概念:任务队列和工作线程。任务队列保存要执行的任务,工作线程不停的从任务队列取要执行的任务。
优点
:1.线程重用,避免线程开销。2. 任务过多,通过排队避免创建更多线程,减少系统资源消耗和竞争,确保任务有序完成。
线程池大小
:corePoolSize,核心线程池大小. maxmumPoolSize,最大线程个数。keepAliveTime&unit,空闲线程存活时间。
workQueue
:任务队列,线程池要求传入的队列是阻塞队列的实现。LinkedBlockingQueue,ArrayBlockedQueue,PriorityBlockingQueue,Synchronose(没有实际存储空间的同步阻塞队列)。注意
如果用的是无界队列,那么maxmumPoolSize参数将失去意义,因为线程大于corePoolSize后会一直排队。对于synChronoseQueue,当尝试排队时,只有有空闲线程,才会入队成功,否则一直创建线程,直到maxmunPoolSize.
corePoolSize
:一般情况下,有新任务到来时,如果线程个数小于corePoolSize则创建新线程,即使有线程处于空闲状态。如果线程数大于等于corePoolSize,则会尝试
入队,注意不是阻塞入队。小于corePoolSize的线程,叫做核心线程,核心线程不会事先创建,任务来的时候才创建,核心线程不会考虑参数keepAliveTime。
maxmunPoolSize
:线程池可创建的最大线程数,新任务到来时,如果线程数大于corePoolSize,并且队列也满了,当前线程数少于maxmunPoolSize,则会继续创建线程来完成执行任务。
keepAliveTime
:非核心线程的存活时间,注意核心线程不会在意这个属性。
任务拒绝策略
:队列有界并且maxmunPoolSize有限,两者都满的情况下回触发拒绝策略。AbortPolicy(抛异常),DiscardPolicy(静默处理,不执行,不抛异常),DiscardOldestPolicy(抛弃等待时间最长的任务,自己入队),CallerRunPolicy(调用线程自己执行)
线程工厂
:默认使用DefaultThreadFactory。主要功能为创建一个线程,给线程设置一个名称,设置deamon属性,设置标准优先级。
newSingleThreadExecutor
:core,maxmun都为1,队列为无界队列,空闲超时时间为0,即不会超时。使用场景为所有任务按顺序执行。
newFixedThreadPool
: 相比newSingleThreadExecutor,core, maxmum的值是可设置的。其他都一样,也是无界队列,线程不会超时。
newCachedThreadPool
: core=0, maxmun=Integer.max, keepAlivetime=60s,queue=Synchronose. 含义为:新任务到来时候,如果有空闲线程,则使用空闲线程,如果没有,总是创建新线程。线程60秒超时。
场景
:1.系统负载很高,一般使用newFixedThreadPool,通过排队,保证有足够的资源执行实际的任务。2.负载不高,任务都比较小,使用newCachedThreadPool,每次都创建新线程来执行。效率高。
死锁
:试想一种场景,使用newSingleThreadExecutor,只有单线程,任务A先执行,执行过程中通过相同的执行服务提交了任务B,任务A依赖任务B执行,这个时候,任务A等待任务B结果,任务B等待被调度。
概念
:一个ReadWriteLock可以获取两个锁,一个readLock,一个writeLock. 读读可以并行,读写不可以,写写也不可以。
原理
:虽然有两个锁,但是内部只有一个等待队列,两个锁共享这个等待队列。有一个变量用来完成CAS操作。
写锁获取
:当前没有任何线程持有任何锁,则可以获取。否则等待
写锁释放
:释放后唤醒等待队列第一个等待线程,唤醒的可能是读锁,可以是写锁。
读锁的获取
:只有没有写锁就可以获取读锁,另外读锁获取后,会检查等待等待队列,依次唤醒前面的读锁,直到遇到写锁。如果有写锁则等待。
读锁释放
:读锁释放后,还需要检查当前读锁数量是不是为0,为0则唤醒下一个线程。
概念
:锁解决了一个线程访问一个资源的问题,semaphore解决了多个线程访问多个资源的问题。简单来讲,就是限制并发数。初始化需要指定许可数,semaphore可以批量获取许可数,可以释放许可数,可以尝试获取,支持限时获取等功能。需要注意的是semaphore不支持重入,线程重入会重复消耗许可数
。
概念
:类似于一个门栓,一开始是关闭的,所有想通过的线程需要等待,然后开始倒计时,倒计时变为0后,门栓打开,等待的线程可以通过。需要注意的时候它是一次性的
,打开就不能关上了。
主要方法
:await,检查计数是否为0,大于0就等待。await可以被中断,可以限时。countDown,检查计数,如果为0直接返回,反正减一,如果减一后计数变为0,则唤醒所有等待的线程。
场景
: 1.同时开始,子线程await,主线程countDown,计数为1. 2.主从协作,即主线程需要等待子线程的结果。主线程await,子线程完成后countDown.
概念
:相当于一个栅栏,所有线程到达栅栏后停下来等待其他线程,等所有线程到达后一起通过。它是循环的,可用作重复的同步。CyclicBarrie特别适合并行迭代计算,每个线程负责一部分计算,然后在栅栏处等待其他线程到达,所有线程到齐后交互数据,进行下一次迭代。
主要方法
:await,等待其他线程到达栅栏,调用说明自己已经到了栅栏,如果自己是最后一个达到的,需要执行可选的命令。执行后唤醒所有等待的线程,重置同步计数,循环使用。await可以被中断,可以限定等待时长,中断或者超时后会抛出异常。
BrokenBarrieException
:栅栏被破坏了,在CyclicBarrie中,线程之间是互相影响的,只要其中一个线程调用await超时或者抛出了异常,栅栏就会被破坏。此外,如果栅栏动作抛异常,也会被破坏。
Cyclic Barrie vs CountDownLatch
:1.CountDownLatch涉及的是不同的角色,有的负责倒计时,有的负责等待倒计时,负责倒计时的和等待倒计时的线程可以有多个,用于不同角色的写作。CyclicBarrie的角色都是一样的,用于同一角色协调一致。3.CountdownLatch是一次性的,CyclicBarrie是重复的。
概念
:线程本地变量,每一个线程都有同一个变量的本地拷贝。
原理
:在ThreadLocal获取变量的时候,其实是获取的是Thread自己内部的一个ThreadLocalMap. 即ThreadLocal.set(),最终set到了当前线程内部的一个map,键为当前的ThreadLocal,值为set的value.
1.读多写少的场景使用ReentrantReadWriteLock代替ReentrantLock;
2.使用semaphore控制并发访问数
3.使用CountDownLatch控制不同角色的协作。
4.使用CyclicBarrie控制同一角色的协调一致。
5.ThreadLocal,线程本地变量