Lock
ReentrantLock
可重入锁
获取到锁的线程可以多次调用lock()方法对共享资源重复加锁而不会被锁阻塞,对于ReentrantLock来说获取锁是将state从0置成1,而重入是将state加1,每一次释放锁都是将state减1,锁的最终释放是将state减为0。
公平锁和非公平锁
如果一个锁是公平的,那么这个锁的获取顺序应该是请求的绝对时间的顺序,也就是FIFO;否则就是非公平锁。ReentrantLock支持通过构造器传入参数来确定是公平锁还是非公平锁。对于非公平锁来说,会先去进行一次CAS然后再去执行acquire()方法,而公平锁会直接去执行acquire()方法;对于公平锁来说每次tryAcquire时还会去判断当前队列里在它前面还有没有结点,没有的话才会去执行CAS操作,而非公平锁会直接去执行CAS操作。
ReentrantReadWriteLock
读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大的提升。
特点:公平性选择、重进入、锁降级
读写状态的设置
读写锁使用AQS的state属性来充当状态,高16位为读状态,低16位为写状态。
写锁的获取和释放
写锁是一个支持可重进入的排他锁,如果读锁或则写锁已经被获取则获取写锁的线程需要去等待至所有的读锁或写锁释放,当写锁被获取时,获取读锁的线程会等待到写锁释放。写锁支持重进入,每重进一次就将写状态加1,释放一次就将写状态减1,写状态为0时写锁完全释放。
读锁的获取和释放
读锁是一个支持重进入的共享锁,能被多个线程同时获取。读状态为所有线程获取读锁次数的总和(算上重进入),而每个线程各自重进入读锁的次数保存在ThreadLocal中,由线程自身维护。
锁降级
指先获取到了写锁,再去获取读锁,然后释放掉写锁,这时就由写锁切换到了读锁,完成了锁降级。
好处:
- 缩短写锁的占用时间,提高并发度;
- 保证了数据的可见性,防止出现线程A释放掉写锁后,线程B获取到写锁并对数据进行了修改,然后线程A去读数据读到脏数据的问题。
Condition
Lock接口有一个抽象方法newCondition(),返回的是一个Condition。Condition接口也提供了类似于Object的监视器方法来实现等待通知模式。AQS的内部类ConditionObject实现了Condition,每个Condition对象都关联着一个FIFO队列,每个等待在该Condition上的线程都会被包装成一个结点(同步队列和等待队列中的结点类型都是AQS的内部类Node)添加到队列尾部。监视器模型下,一个对象拥有一个同步队列和一个等待队列;而Lock拥有一个同步队列和多个等待队列。
相关方法:
方法名 | 描述 |
---|---|
await() | 当前线程释放锁并进入到Condition对应的等待队列中等待 |
signal()/signalAll() | 唤醒等待在Condition上的线程 |
注意:
- 调用方法前需要先获取到创建出Condition的Lock对应的锁
- 从await()方法返回的前提是该线程获取到了相应的锁
Lock与synchronized的对比
- synchronized的基于Monitor的重量级锁会去向操作系统申请锁资源,而Lock是基于volatile+CAS+CLH队列的完全运行在用户态的。
- synchronized隐式的加锁和解锁,而Lock需要显示的获取和释放锁。
- Lock支持更多的功能,如:公平锁与非公平锁、可中断的获取锁、可超时的获取锁等功能。
- 使用synchronized时,如果一个线程获取不到锁而被阻塞在锁上时,对该线程进行中断操作后线程的中断标志位会被修改,但是线程依旧会被阻塞在synchronized上等待获取锁;而Lock的lockInterruptibly()方法支持可中断的获取锁,如果当前线程被中断,会立刻返回。
并发工具类
LockSupport
作用
让一个线程进入阻塞状态,并且可以唤醒指定线程
使用
- LockSupport.park()让当前线程进入阻塞状态
- LockSupport.unpark(要唤醒的线程)唤醒指定线程
注意
- unpark()可以先于park()使用,这样的话线程下一次遇到park()就不会进入阻塞状态(如果再遇到还会进入阻塞状态)
- park()并不会释放锁,它的作用只是阻塞当前线程
CountDownLatch
作用
计数,当减少指定次数后继续向下执行(一般用于等所有线程都执行完以后继续向下执行)
使用
- CountDownLatch latch = new CountDownLatch(N);
- 使用latch.countDown()方法来将N减一
- 使用latch.await()方法来等待N减为0,然后再向下运行接下来的代码
CyclicBarrier
作用
让一组线程到达一个屏障时被阻塞,当拦截的线程数达到某个值以后,放行这些线程并重新从0开始拦截线程。
使用
- CyclicBarrier barrier = new CyclicBarrier(要拦截的线程数, 当达到这些线程数以后执行的操作);
- 在线程刚开始的地方使用barrier.await()方法来拦截线程,当达到预定值以后放行线程并执行指定的操作,然后重新去拦截。
Semaphore
作用
控制并发线程数,即允许多少个线程在某一时刻同时执行。
使用
- Semaphore s = new Semaphore(允许同时执行的线程数);
- 在线程开始时使用s.acquire(),如果当前被允许执行的线程数还有剩余则继续向下执行,并且被允许执行的线程数减一,否则进入阻塞。
- 在线程执行结束以后使用s.release(),被允许执行的线程数加一
注意:默认是非公平的获取,可以在创建对象时传入参数true开启公平锁。
Exchanger
作用
用于两个线程之间交换数据,它提供了一个同步点,然后在这个同步点两个线程完成数据交换。如果第一个线程先执行了exchange()方法,则它会一直等待第二个线程也执行exchange()方法,当两个线程都到达同步点时就可以交换数据了。
使用
- Exchanger exchanger = new Exchanger<>;
- 在一个线程里调用exchanger.exchange(提供的要交换的数据),然后进入阻塞状态,该方法的返回值即为交换得到的数据。
- 另一个线程调用exchanger.exchange(提供的要交换的数据),完成数据交换(方法返回值即为交换后的数据),然后两个线程解除阻塞向后执行。
如果你想了解更多我对编程和人生的思考,请关注公众号:青云学斋