Synchronized是jvm内置的锁;而java.util.concurrent包下面的Lock锁是大佬(Doug Lea)用java代码实现的显示锁。
Juc包下面锁相比jvm内置的锁更加灵活。围绕着AQS(AbstractQueuedSynchronizer)实现了一系列性质的锁,比如共享/独占,公平/非公平,重入,阻塞等待。
以ReentrantLock为例,它有一个字段是sync,是其内部类Sync,Sync类继承了AbstractQueuedSynchronizer,同时它还有两个子类一个NonfairSync(非公平),一个是FairSync(公平的),显然这两个就是实现分别实现了公平锁和非公平锁。
再看AbstractQueuedSynchronizer的父类AbstractOwnableSynchronizer中有一个字段是exclusiveOwnerThread,从这个字段注释可以看出来(The current owner of exclusive mode synchronization.)这个是记录独占线程的。
另外这AbstractQueuedSynchronizer类中还有一个内部类Node,Node中有一系列的标记,还有一个Thread来标记当前的线程。借此来实现锁相关队列。比如同步队列借助Node的prev和next字段来实现的双向链表。条件队列是借助nextWaiter来实现的单向链表。
同时有一个标记waitStatus来记录当前节点的状态(CANCELLED,SIGNAL,CONDITION,PROPAGATE)。比方说没抢到锁的就将这个线程新建一个Node扔到队列里面去等着。简单的提了下队列,再说下公平和非公平,公平就是不论怎么样都乖乖的去队列排队等锁;非公平就是上来先去抢锁,抢到就先执行了,没抢到再去队列排队。
以上这些就是AQS比较重要的点,整个加锁实现的逻辑都是围绕这些东西来玩的。
上面简单接单介绍AQS中的几个比较关键的点。下面通过代码debug来看下这些东西
首先我们看下单独运行一个线程的时候 关注ReentrantLock这个类中的sync字段的属性变化。我们会看到一个线程的时候同步器(sync)的head,tail字段(这两个就是上面说到Node类)是null,exclusiveOwnerThread是我们当前的这个这个线程,state字段第一次加锁的时候1,第二次加锁会+1,释放一次锁会-1。当state减到0的时候说明当前线程已经完全释放了锁,以此来实现锁重入的的功能。
代码片段:
public class SyncTest { private Lock lock = new ReentrantLock(true); public static void main(String[] args) { SyncTest syncTest = new SyncTest(); new Thread(()->syncTest.testLock(), "线程1").start(); } public void testLock() { lock.lock(); System.out.println("线程:--->" + Thread.currentThread().getName() + "第一次加锁"); lock.lock(); System.out.println("线程:--->" + Thread.currentThread().getName() + "第二次加锁"); lock.unlock(); System.out.println("线程:--->" + Thread.currentThread().getName() + "释放第一个锁"); lock.unlock(); System.out.println("线程:--->" + Thread.currentThread().getName() + "释放第一个锁"); } }
三个线程执行:
会看见head 和tail都有值,并且可以看见剩下的两个线程在队列里面,(注意这里head其实只是一个空的头结点,真的头结点是当前执行的线程。这里面head结点主要是为了方便操作声明了)。
以ReentrantLock中的公平为例:
1.拿到锁的线程,如果加锁标记state是0,那么就通过CAS操作将state改成1,并且将独占线程设置为当前线程,否则判断当前线程是否是独占线程,如果是则重入
2.如果都不是 就放入队列中去排队
3.释放锁的时候,当将state修改成0的时候tryRelease方法返回的才是true才会去唤醒后续的结点
juc下面的锁基本就是借助AQS(AbstractQueuedSynchronizer队列同步器的各种实现)和Unsafe这个类调用系统层面的CAS操作保证属性的修改,以及线程阻塞和唤醒操作park和unpark,其次就是很多属性都是使用了volatile来保证一个线程修改其他线程对其状态的更改立马可见。