Lock接口、AbstractQueuedSynchronizer队列同步器、重入锁、读写锁
一、Lock接口
Lock接口提供了一些方法,比如lock(),tryLock()等方法,像ReentrantLock就是基于队列同步器来实现的
二、AbstractQueuedSynchronizer
1.什么是队列同步器
队列同步器是一个抽象类,提供了实现线程队列获取同步状态的一系列方法(简单理解就是加锁),包含队列节点(是一个FIFO模型,先进先出),队列节点属性中包含:头(head)、尾(tail)、前置(prev)、后置(next)节点,提供了一系列获取锁的方法,这些方法都是通过CAS自旋的方式进行写的操作。
2.队列同步器需要重写的方法和已经提供的方法
需要重写的方法就是try开头的方法,用于获取同步状态,而已提供的方法都会执行需要重写的方法
已提供的这些方法就包含了:
独占式获取同步状态
独占式释放同步状态
共享式获取同步状态
共享式释放同步状态
查询同步状态
3.独占式
获取锁:
新的线程添加到队列节点中,并设置此节点自旋CAS判断,判断前置节点是否是头节点并尝试获取同步状态,判断是并获取成功则设置节点为头节点并执行,判断否则继续自旋判断
释放锁
4.共享式
共享式与独占式的区别在于多个线程能否同时获取到同步状态
获取锁与释放锁还是和独占式类似,通过自旋判断上前置节点是否是头节点来进行尝试获取同步状态
5.独占式超时获取同步状态
就是指在指定时间之内获取锁,获取到了返回true,否则返回false。这是 synchronized 所不具备的。另外, synchronized会让中断( interrupte )的线程一直阻塞,而队列同步器可以立即返回并抛出异常,节省资源。
独占式超时获取同步状态 和 独占式 相比较起来,前半部分都一样,自旋判断前置节点是否为头节点,但是,在判断为false时,有一个判断其是否超时的逻辑并返回。
三、重入锁ReentrantLock
特点
对于同一个线程而言,多次使用重入锁进行 lock() 操作时,不会造成线程阻塞,这就是重入锁的名字来由;
由于重入锁可以多次对同一个线程上锁,这里面有一个对上锁次数的计数,而只有将计数的次数释放完了之后,重入锁才算是释放了;
另外,重入锁还有公平锁与非公平锁之分。公平锁,就像它的名字一样,哪个线程先获得锁,等待时间最久,那么它就可以优先获得锁去执行。而非公平锁,就是不管哪个线程等的最久,谁先抢到锁,就是谁的,没有判断公平的逻辑;
非公平锁
重入锁的默认实现就是非公平锁,非公平锁会在上锁分别尝试两次CAS抢锁,如果两次CAS抢锁都失败的话,那么会进入到同步队列中等待
公平锁
而公平锁在每一次尝试抢锁的时候会去判断前置节点是否存在,如果存在,就说明自己在队列中位置较后就不会去尝试抢锁
非公平锁和公平锁的总结
公平锁仅仅是保证了线程执行的FIFO先进先出的规则,但是却没有非公平锁的效率高,因为非公平锁不会像公平锁那样对资源进行占用并判断是否是可获得锁的线程,也就少了很多开销。
详解
https://www.cnblogs.com/lcmlyj/p/14010607.html
https://www.cnblogs.com/lcmlyj/p/16391154.html
四、读写锁ReadWriteLock
特点
一个读写锁,可以让多个线程同时获取读锁,但是在获取写锁的时候会阻塞。而获取了写锁之后,所有获取读锁的操作都会被阻塞。读写锁也拥有公平锁和非公平锁的特点。读写锁更加适用于例如缓存,缓存在多线程读的时候可以同时获得读锁,而写的操作需要获取写锁。
原理
读写锁只通过一个值来判断读写状态的,是通过32位分断的方式。前16位是读锁状态,而后16位是写锁状态。获取读锁的时候会用同步状态与读写状态值做位运算&,而获取写锁的时候会用位运算符>>>。
读锁
多个线程可以同时获取读锁,而此时如果有线程想获取写锁,那么会被阻塞
写锁
只有一个线程可以获取写锁,写锁是排它锁。