Java系列9线程知识详解计数器,进程,线程调度
CyclicBarrier、CountDownLatch、Semaphore的用法
1。CountDownLatch(线程计数器)
CountDownLatch类似于java。util。concurrent包下,利用它可以实现类似计数器的功能。比如有一个任务A,它要等待其他4个任务执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。
finalCountDownLatchlatchnewCountDownLatch(2);
newThread(){publicvoidrun(){
System。out。println(子线程Thread。currentThread()。getName()正在执行);
Thread。sleep(3000);
System。out。println(子线程Thread。currentThread()。getName()执行完毕);
latch。countDown();
};}。start();
newThread(){
publicvoidrun(){
System。out。println(子线程Thread。currentThread()。getName()正在执行);
Thread。sleep(3000);
System。out。println(子线程Thread。currentThread()。getName()执行完毕);
latch。countDown();
};}。start();
System。out。println(等待2个子线程执行完毕。。。);
latch。await();
System。out。println(2个子线程已经执行完毕);
System。out。println(继续执行主线程);
}
2。CyclicBarrier(回环栅栏等待至barrier状态再全部同时执行)
字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。CyclicBarrier中最重要的方法就是await方法,它有2个重载版本:
1。publicintawait():用来挂起当前线程,直至所有线程都到达barrier状态再同时执行后续任务;
2。publicintawait(longtimeout,TimeUnitunit):让这些线程等待至一定的时间,如果还有线程没有到达barrier状态就直接让到达barrier的线程执行后续任务。具体使用如下,另外CyclicBarrier是可以重用的。
publicstaticvoidmain(String〔〕args){
intN4;
CyclicBarrierbarriernewCyclicBarrier(N);
for(inti0;iN;i)
newWriter(barrier)。start();
}
staticclassWriterextendsThread{
privateCyclicBarriercyclicBarrier;
publicWriter(CyclicBarriercyclicBarrier){
this。cyclicBarriercyclicBarrier;
}
Override
publicvoidrun(){
try{
Thread。sleep(5000);
以睡眠来模拟线程需要预定写入数据操作System。out。println(线程Thread。currentThread()。getName()写入数据完毕,等待其他线程写入完毕);
cyclicBarrier。await();
}catch(InterruptedExceptione){
e。printStackTrace();
}catch(BrokenBarrierExceptione){
e。printStackTrace();
}
System。out。println(所有线程写入完毕,继续处理其他任务,比如数据操作);
}
}
3。Semaphore(信号量控制同时访问的线程个数)
Semaphore翻译成字面意思为信号量,Semaphore可以控制同时访问的线程个数,通过acquire()获取一个许可,如果没有就等待,而release()释放一个许可。Semaphore类中比较重要的几个方法:
1。publicvoidacquire():用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。
2。publicvoidacquire(intpermits):获取permits个许可
3。publicvoidrelease(){}:释放许可。注意,在释放许可之前,必须先获获得许可。
4。publicvoidrelease(intpermits){}:释放permits个许可上面4个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法
1。publicbooleantryAcquire():尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
2。publicbooleantryAcquire(longtimeout,TimeUnitunit):尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
3。publicbooleantryAcquire(intpermits):尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
4。publicbooleantryAcquire(intpermits,longtimeout,TimeUnitunit):尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
5。还可以通过availablePermits()方法得到可用的许可数目。
例子:若一个工厂有5台机器,但是有8个工人,一台机器同时只能被一个工人使用,只有使用完了,其他工人才能继续使用。那么我们就可以通过Semaphore来实现:intN8;工人数
SemaphoresemaphorenewSemaphore(5);机器数目
for(inti0;iN;i)
newWorker(i,semaphore)。start();
}
staticclassWorkerextendsThread{
privateintnum;
privateSemaphoresemaphore;
publicWorker(intnum,Semaphoresemaphore){
this。numnum;
this。semaphoresemaphore;
}
Override
publicvoidrun(){
try{
semaphore。acquire();
System。out。println(工人this。num占用一个机器在生产。。。);
Thread。sleep(2000);
System。out。println(工人this。num释放出机器);
semaphore。release();
}catch(InterruptedExceptione){
e。printStackTrace();
}
}
CountDownLatch和CyclicBarrier都能够实现线程之间的等待,只不过它们侧重点不同;CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;另外,CountDownLatch是不能够重用的,而CyclicBarrier是可以重用的。
Semaphore其实和锁有点类似,它一般用于控制对某组资源的访问权限。
volatile关键字的作用(变量可见性、禁止重排序)
Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。volatile变量具备两种特性,volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
变量可见性其一是保证该变量对所有线程可见,这里的可见性指的是当一个线程修改了变量的值,那么新的值对于其他线程是可以立即获取的。
禁止重排序volatile禁止了指令重排。比sychronized更轻量级的同步锁在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。volatile适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。
当对非volatile变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的CPUcache中。而声明变量是volatile的,JVM保证了每次读变量都从内存中读,跳过CPUcache这一步。适用场景值得说明的是对volatile变量的单次读写操作可以保证原子性的,如long和double类型变量,但是并不能保证i这种操作的原子性,因为本质上i是读、写两次操作。在某些场景下可以代替Synchronized。但是,volatile的不能完全取代Synchronized的位置,只有在一些特殊的场景下,才能适用volatile。总的来说,必须同时满足下面两个条件才能保证在并发环境的线程安全:
(1)对变量的写操作不依赖于当前值(比如i),或者说是单纯的变量赋值(booleanflagtrue)。
(2)该变量没有包含在具有其他变量的不变式中,也就是说,不同的volatile变量之间,不能互相依赖。只有在状态真正独立于程序内其他内容时才能使用volatile。
如何在两个线程之间共享数据
Java里面进行多线程通信的主要方式就是共享内存的方式,共享内存主要的关注点有两个:可见性和有序性原子性。Java内存模型(JMM)解决了可见性和有序性的问题,而锁解决了原子性的问题,理想情况下我们希望做到同步和互斥。有以下常规实现方法:将数据抽象成一个类,并将数据的操作作为这个类的方法
1。将数据抽象成一个类,并将对这个数据的操作作为这个类的方法,这么设计可以和容易做到同步,只要在方法上加synchronized
publicclassMyData{
privateintj0;
publicsynchronizedvoidadd(){
j;
System。out。println(线程Thread。currentThread()。getName()j为:j);
}
publicsynchronizedvoiddec(){
j;
System。out。println(线程Thread。currentThread()。getName()j为:j);
}
publicintgetData(){
returnj;
}
}
publicclassAddRunnableimplementsRunnable{
MyDatadata;
publicAddRunnable(MyDatadata){
this。datadata;
}
publicvoidrun(){
data。add();
}
}
publicclassDecRunnableimplementsRunnable{
MyDatadata;
publicDecRunnable(MyDatadata){
this。datadata;
}
publicvoidrun(){
data。dec();
}
}
publicstaticvoidmain(String〔〕args){
MyDatadatanewMyData();
RunnableaddnewAddRunnable(data);
RunnabledecnewDecRunnable(data);
for(inti0;i2;i){
newThread(add)。start();
newThread(dec)。start();
}
Runnable对象作为一个类的内部类
2。将Runnable对象作为一个类的内部类,共享数据作为这个类的成员变量,每个线程对共享数据的操作方法也封装在外部类,以便实现对数据的各个操作的同步和互斥,作为内部类的各个Runnable对象调用外部类的这些方法。
publicclassMyData{
privateintj0;
publicsynchronizedvoidadd(){
j;
System。out。println(线程Thread。currentThread()。getName()j为:j);
}
publicsynchronizedvoiddec(){
j;
System。out。println(线程Thread。currentThread()。getName()j为:j);
}
publicintgetData(){
returnj;
}
}
publicclassTestThread{
publicstaticvoidmain(String〔〕args){
finalMyDatadatanewMyData();
for(inti0;i2;i){
newThread(newRunnable(){
publicvoidrun(){
data。add();
}
})。start();
newThread(newRunnable(){
publicvoidrun(){
data。dec();
}
})。start();
}
}
}
ThreadLocal作用(线程本地存储)
ThreadLocal,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,ThreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。ThreadLocalMap(线程的一个属性)
1。每个线程中都有一个自己的ThreadLocalMap类对象,可以将线程自己的对象保持到其中,各管各的,线程可以正确的访问到自己的对象。
2。将一个共用的ThreadLocal静态实例作为key,将不同对象的引用保存到不同线程的ThreadLocalMap中,然后在线程执行的各处通过这个静态ThreadLocal实例的get()方法取得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。
3。ThreadLocalMap其实就是线程里面的一个属性,它在Thread类中定义ThreadLocal。ThreadLocalMapthreadLocalsnull;
使用场景最常见的ThreadLocal使用场景为用来解决数据库连接、Session管理等。
privatestaticfinalThreadLocalthreadSessionnewThreadLocal();
publicstaticSessiongetSession()throwsInfrastructureException{
Sessions(Session)threadSession。get();
try{
if(snull){
sgetSessionFactory()。openSession();
threadSession。set(s);
}
}catch(HibernateExceptionex){
thrownewInfrastructureException(ex);
}
returns;
}
synchronized和ReentrantLock的区别
1。两者的共同点:
1。都是用来协调多线程对共享对象、变量的访问
2。都是可重入锁,同一线程可以多次获得同一个锁
3。都保证了可见性和互斥性
2。两者的不同点:
1。ReentrantLock显示的获得、释放锁,synchronized隐式获得释放锁
2。ReentrantLock可响应中断、可轮回,synchronized是不可以响应中断的,为处理锁的不可用性提供了更高的灵活性
3。ReentrantLock是API级别的,synchronized是JVM级别的
4。ReentrantLock可以实现公平锁
5。ReentrantLock通过Condition可以绑定多个条件
6。底层实现不一样,synchronized是同步阻塞,使用的是悲观并发策略,lock是同步非阻塞,采用的是乐观并发策略
7。Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现。
8。synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。
9。Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。
10。通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
11。Lock可以提高多个线程进行读操作的效率,既就是实现读写锁等。
ConcurrentHashMap并发
1。减小锁粒度
减小锁粒度是指缩小锁定对象的范围,从而减小锁冲突的可能性,从而提高系统的并发能力。减小锁粒度是一种削弱多线程锁竞争的有效手段,这种技术典型的应用是ConcurrentHashMap(高性能的HashMap)类的实现。对于HashMap而言,最重要的两个方法是get与set方法,如果我们对整个HashMap加锁,可以得到线程安全的对象,但是加锁粒度太大。Segment的大小也被称为ConcurrentHashMap的并发度。
2。ConcurrentHashMap分段锁
ConcurrentHashMap,它内部细分了若干个小的HashMap,称之为段(Segment)。默认情况下一个ConcurrentHashMap被进一步细分为16个段,既就是锁的并发度。
如果需要在ConcurrentHashMap中添加一个新的表项,并不是将整个HashMap加锁,而是首先根据hashcode得到该表项应该存放在哪个段中,然后对该段加锁,并完成put操作。在多线程环境中,如果多个线程同时进行put操作,只要被加入的表项不存放在同一个段中,则线程间可以做到真正的并行。
ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment是一种可重入锁ReentrantLock,在ConcurrentHashMap里扮演锁的角色,HashEntry则用于存储键值对数据。一个ConcurrentHashMap里包含一个Segment数组,Segment的结构和HashMap类似,是一种数组和链表结构,一个Segment里包含一个HashEntry数组,每个HashEntry是一个链表结构的元素,每个Segment守护一个HashEntry数组里的元素,当对HashEntry数组的数据进行修改时,必须首先获得它对应的Segment锁。
Java中用到的线程调度
1。抢占式调度:
抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。
2。协同式调度:
协同式调度指某一线程执行完后主动通知系统切换到另一线程上执行,这种模式就像接力赛一样,一个人跑完自己的路程就把接力棒交接给下一个人,下个人继续往下跑。线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步问题,但它有一个致命弱点:如果一个线程编写有问题,运行到一半就一直堵塞,那么可能导致整个系统崩溃。
3。JVM的线程调度实现(抢占式调度)
java使用的线程调使用抢占式调度,Java中线程会按优先级分配CPU时间片运行,且优先级越高越优先执行,但优先级高并不代表能独自占用执行时间片,可能是优先级高得到越多的执行时间片,反之,优先级低的分到的执行时间少但不会分配不到执行时间。
4。线程让出cpu的情况:
1。当前运行线程主动放弃CPU,JVM暂时放弃CPU操作(基于时间片轮转调度的JVM操作系统不会让线程永久放弃CPU,或者说放弃本次时间片的执行权),例如调用yield()方法。
2。当前运行线程因为某些原因进入阻塞状态,例如阻塞在IO上。
3。当前运行线程结束,即运行完run()方法里面的任务。
进程调度算法
1。优先调度算法
1。先来先服务调度算法(FCFS)
当在作业调度中采用该算法时,每次调度都是从后备作业队列中选择一个或多个最先进入该队列的作业,将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。在进程调度中采用FCFS算法时,则每次调度是从就绪队列中选择一个最先进入该队列的进程,为之分配处理机,使之投入运行。该进程一直运行到完成或发生某事件而阻塞后才放弃处理机,特点是:算法比较简单,可以实现基本上的公平。
2。短作业(进程)优先调度算法
短作业优先(SJF)的调度算法是从后备队列中选择一个或若干个估计运行时间最短的作业,将它们调入内存运行。而短进程优先(SPF)调度算法则是从就绪队列中选出一个估计运行时间最短的进程,将处理机分配给它,使它立即执行并一直执行到完成,或发生某事件而被阻塞放弃处理机时再重新调度。该算法未照顾紧迫型作业。
2。高优先权优先调度算法
为了照顾紧迫型作业,使之在进入系统后便获得优先处理,引入了最高优先权优先(FPF)调度算法。当把该算法用于作业调度时,系统将从后备队列中选择若干个优先权最高的作业装入内存。当用于进程调度时,该算法是把处理机分配给就绪队列中优先权最高的进程。
1。非抢占式优先权算法
在这种方式下,系统一旦把处理机分配给就绪队列中优先权最高的进程后,该进程便一直执行下去,直至完成;或因发生某事件使该进程放弃处理机时。这种调度算法主要用于批处理系统中;也可用于某些对实时性要求不严的实时系统中。
2。抢占式优先权调度算法
在这种方式下,系统同样是把处理机分配给优先权最高的进程,使之执行。但在其执行期间,只要又出现了另一个其优先权更高的进程,进程调度程序就立即停止当前进程(原优先权最高的进程)的执行,重新将处理机分配给新到的优先权最高的进程。显然,这种抢占式的优先权调度算法能更好地满足紧迫作业的要求,故而常用于要求比较严格的实时系统中,以及对性能要求较高的批处理和分时系统中。
2高响应比优先调度算法
在批处理系统中,短作业优先算法是一种比较好的算法,其主要的不足之处是长作业的运行得不到保证。如果我们能为每个作业引入前面所述的动态优先权,并使作业的优先级随着等待时间的增加而以速率a提高,则长作业在等待一定的时间后,必然有机会分配到处理机。该优先权的变化规律可描述为:
(1)如果作业的等待时间相同,则要求服务的时间愈短,其优先权愈高,因而该算法有利于短作业。
(2)当要求服务的时间相同时,作业的优先权决定于其等待时间,等待时间愈长,其优先权愈高,因而它实现的是先来先服务。
(3)对于长作业,作业的优先级可以随等待时间的增加而提高,当其等待时间足够长时,其优先级便可升到很高,从而也可获得处理机。简言之,该算法既照顾了短作业,又考虑了作业到达的先后次序,不会使长作业长期得不到服务。因此,该算法实现了一种较好的折衷。当然,在利用该算法时,每要进行调度之前,都须先做响应比的计算,这会增加系统开销。
3。基于时间片的轮转调度算法
1。时间片轮转法
在早期的时间片轮转法中,系统将所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把CPU分配给队首进程,并令其执行一个时间片。时间片的大小从几ms到几百ms。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;然后,再把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。这样就可以保证就绪队列中的所有进程在一给定的时间内均能获得一时间片的处理机执行时间。
2。多级反馈队列调度算法
(1)应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,,第i1个队列的时间片要比第i个队列的时间片长一倍。
(2)当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n队列便采取按时间片轮转的方式运行。
(3)仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1(i1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1(i1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。
在多级反馈队列调度算法中,如果规定第一个队列的时间片略大于多数人机交互所需之处理时间时,便能够较好的满足各种类型用户的需要。
什么是CAS(比较并交换乐观锁机制锁自旋)
1。概念及特性
CAS(CompareAndSwapSet)比较并交换,CAS算法的过程是这样:它包含3个参数CAS(V,E,N)。V表示要更新的变量(内存值),E表示预期值(旧的),N表示新值。当且仅当V值等于E值时,才会将V的值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS返回当前V的真实值。
CAS操作是抱着乐观的态度进行的(乐观锁),它总是认为自己可以成功完成操作。当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。
2。原子包java。util。concurrent。atomic(锁自旋)
JDK1。5的原子包:java。util。concurrent。atomic这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由JVM从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。
相对于对于synchronized这种阻塞算法,CAS是非阻塞算法的一种常见实现。由于一般CPU切换时间比CPU指令集操作更加长,所以J。U。C在性能上有了很大的提升。如下代码:
publicclassAtomicIntegerextendsNumberimplementsjava。io。Serializable{
privatevolatileintvalue;
publicfinalintget(){
returnvalue;
}
publicfinalintgetAndIncrement(){
for(;;){CAS自旋,一直尝试,直达成功
intcurrentget();
intnextcurrent1;
if(compareAndSet(current,next))
returncurrent;
}
}
publicfinalbooleancompareAndSet(intexpect,intupdate){
returnunsafe。compareAndSwapInt(this,valueOffset,expect,update);
}
}
getAndIncrement采用了CAS操作,每次从内存中读取数据然后将此数据和1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。而compareAndSet利用JNI来完成CPU指令的操作。
3。ABA问题
CAS会导致ABA问题。CAS算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。
比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的。
部分乐观锁的实现是通过版本号(version)的方式来解决ABA问题,乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行1操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现ABA问题,因为版本号只会增加不会减少。
什么是AQS(抽象的队列同步器)
AbstractQueuedSynchronizer类如其名,抽象的队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLockSemaphoreCountDownLatch。
它维护了一个volatileintstate(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)。这里volatile是核心关键词,具体volatile的语义,在此不述。state的访问方式有三种:
getState()
setState()
compareAndSetState()
AQS定义两种资源共享方式
Exclusive独占资源ReentrantLock
Exclusive(独占,只有一个线程能执行,如ReentrantLock)
Share共享资源SemaphoreCountDownLatch
Share(共享,多个线程可同时执行,如SemaphoreCountDownLatch)。
AQS只是一个框架,具体资源的获取释放方式交由自定义同步器去实现,AQS这里只定义了一个接口,具体资源的获取交由自定义同步器去实现了(通过state的getsetCAS)之所以没有定义成abstract,是因为独占模式下只用实现tryAcquiretryRelease,而共享模式下只用实现tryAcquireSharedtryReleaseShared。如果都定义成abstract,那么每个模式也要去实现另一模式下的接口。不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队唤醒出队等),AQS已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法:
1isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
2tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
3tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
4tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
5tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
同步器的实现是ABS核心(state资源状态计数)
同步器的实现是ABS核心,以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state1。此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
以CountDownLatch以例,任务分为N个子线程去执行,state也初始化为N(注意N要与线程个数一致)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state0),会unpark()主调用线程,然后主调用线程就会从await()函数返回,继续后余动作。
ReentrantReadWriteLock实现独占和共享两种方式
一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquiretryRelease、tryAcquireSharedtryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。
实验室仪器管理规定为了妥善保管使用实验室仪器,充分发挥实验室仪器作用,确保实验能顺利做好,制定了仪器的相关管理规定,下面品学网小编给大家介绍关于实验室仪器管理规定的相关资料,希望对您有所帮助。实……
万圣节思念的祝福语已经倾慕你好久好久了,从见到你的第一天起,我就决定非你不嫁。你知道吗?我在阴间好寂寞呀万圣节来陪我好吗?别以为我忘了你,比如说今天万圣节,我第一个想起的就是你,因为你跟鬼……
部队手抄党章心得体会总结参加了这次的部队手抄党章惠东,只有与党的要求一致,才能做ldquo;讲党性、重品行、作表率rdquo;的模范,才能做政治清醒、经济清白、作风清明的表率,才能做服务群众清正清廉的……
四荒地承包合同范本四荒地治理工作一直是农村社会服务中心的重点工作,是制约经济发展的一大难点,只有将其开发治理好才能推动当地经济增长。对于四荒地承包合同你了解多少呢?以下是在品学网小编为大家整理的……
秘书处理人际关系的原则有哪些追求良好的人际关系,实际上是追求现实生活中的ldquo;人和rdquo;境界。秘书为此应遵循一些人际关系的原则。下面是品学网小编给大家搜集整理的秘书处理人际关系的原则文章内容。……
离职证明范本五篇下面是品学网小编精心为大家整理收集的离职证明范本,供大家阅读参考。【离职证明范本】离职证明先生女士小姐,自年月日至年月日在我公司担任(部门)的职务,由于原因提……
公司的通告怎么写通告是在一定范围内公布应当遵守或周知事项时使用的文种,其主要特点是知照性、专业性、约束力。通告具有规范性、业务性、广泛性。以下是小编整理的公司的通告,跟着小编来看看怎么写吧。公……
党员干部学习准则条例心得体会今天小编特意为各位准备了党员干部学习准则条例心得体会,希望能满足大家的阅读需求,看完后有所启示。欢迎阅读党员干部学习准则条例心得体会《中国共产党廉洁自律准则》、《中……
不红时把李易峰当背景板,红了又亲昵搭话,谢娜的势利眼没谁了谢娜最近复出了,但比起上次的复出来说,这次些许安静了不少。没有热搜,没有热度,没有多少人关注,甚至到现在还有人不知道谢娜已经复出了。这反映的是她的关注度已经下降了。……
CBA四强已定,冠军花落谁家?是师徒争冠还是南北大战?从赛季初,辽宁队就以强大的实力,展现出自己争冠的决心!也获得了广大球迷与专家的认可!本赛季,辽宁队送走了小外援梅奥,签约了原山西队大外援莫兰德,用以补强内线的不足,让韩德……
关婷娜2亿豪宅内景曝光,低领睡衣喂羊驼抢眼,网友老赵拍的?关婷娜2亿豪宅内景曝光,低领睡衣喂羊驼抢眼《乡村爱情》相信很多人都看过,以农村为题材左邻右舍之间的家长里短,他们的故事因为平凡接地气,深受大众喜爱。其中谢广坤、刘能、赵四……
这个经典风格,高级不过时去年超时髦的纯欲辣妹风,临近过气。体现新奇特的热辣穿搭风格,逐渐失效。时尚圈迎来的是低调又耐看的OldMoney风。RosettaGetty2022春夏系列像代表了……