阻塞的实现好像都是用for(;;)循环获取
其他笔记:
- AQS是双向链表结构
- synchronized1.6之前,无论有没有竞争(线程交替执行,没有竞争)都会进行操作系统操作进行内核切换,是重量级锁,1.8之后进行了锁分级(无锁、偏向锁、轻量级锁、重量级锁),假如没有竞争只是在JVM层级加上锁,1.8以后性能比reentrantlock要优越,java官方推荐使用synchronized。
- reentrantlock可以选择使用公平锁或者非公平锁,在构造函数加true、false即可。reentrantlock交替执行时,jdk就能解决问题(在JVM层级加上锁)。
- reentrantlock靠AQS类实现,ASQ会使用自旋CAS和调用UNSAFE类的park和unpark方法实现资源的释放和唤醒阻塞队列中的线程。
- 如果AQS队列不为空,那么头结点node中的thread为null。当线程获取到锁后,会把自己所在节点的thread置为null,并且解开与上一个节点的联系,也就是说持有锁的线程永远不会在AQS队列里面。
- AQS自旋体现在,第二个元素(第一个node的thread为空,后面的没必要判断)入队后,还会再次请求获取锁(第一次自旋,因为获取锁的线程可能已经释放锁,但是还还不及unpark下一个线程);当请求不到后,会尝试更改上一个节点的waitstatus,更改完毕后,会再一次请求获取锁;再获取不到就对第二个元素线程就行park。(第二次自旋,状态更改需要安全)。两次自旋也降低了需要park的可能。不过对于第三个元素来说只自旋一次,少了一次入队时的自旋。所以说AQS队列有可能自旋1次,或者2次。
//判断线程是否可以进行下一步的compareAndSetState以及设置当前线程为锁持有线程。
//h != t判断等待队列是否为空。
//(s = h.next) == null这个条件是为了防止当h != t判断完成后,队列还是两个节点,但是
//执行到这一步的时候,第二个节点的线程被唤醒,并且成功获得锁,将原先头部节点的next
//置为了null,导致这个时候其实是没有线程在排队了。这里保证了判断的原子性。
//s.thread != Thread.currentThread()这一行代码是为了解决队列第二个节点线程
//被唤醒后,会再次进行hasQueuedPredecessors()判断,这个时候应该对该线程放行。
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
1、LockSupport
- void park():禁用当前线程调度(会释放资源),直到线程获取许可为止。(Disables the current thread for thread scheduling purposes unless the permit is available.)
- void unpark():如果线程不可用,那么为线程添加许可,然后线程继续执行。否则线程的下一次调用park()方法时,将保证不会被阻塞。如果线程还没有被启动,则该方法不会生效。(Makes available the permit for the given thread, if it was not already available. If the thread was blocked on park then it will unblock. Otherwise, its next call to park is guaranteed not to block. This operation is not guaranteed to have any effect at all if the given thread has not been started.)
Thread thread1 = new Thread(() -> {
LockSupport.park();
ThreadLocalRandom random= ThreadLocalRandom.current();
for(int i=0;i<10;i++) {
System.out.println(random.nextInt(10));
}
});
LockSupport.unpark(thread1);//(1)
thread1.start();//(2)
thread1.join();
上述代码会一直等待,不会有输出。把(2)和(1)的先后顺序调换,就可以使unpark生效,输出随机数。
- void park(Object blocker):类似park(),好处就是在使用jstack pid排查线程阻塞问题时,可以获取更多信息。
- void parkNanos(Object blocker, long nanos):相比park多了超时时间,单位为纳秒。
- void parkUntil(Object blocker, long deadline):deadline单位为毫秒,是从1970年到某一个时间点的毫秒数。
2、ReentrantLock
可重入锁,可设置公平锁或者非公平锁。每次获取锁AQS 的状态值state加1,释放锁状态减1,减到0就会释放锁。主要有以下方法
- void lock():获取锁,如果锁当前没有被其他线程占用并且当前线程之前没有获取过该锁,则当前线程会获取到该锁,然后设置当前锁的拥有者为当前线程, 并设置AQS 的状态值为1,然后直接返回。如果当前线程之前己经获取过该锁,则这次只是简单地把AQS 的状态值加1后返回。如果该锁己经被其他线程持有,则调用该方法的线程会被放入AQS 队列后阻塞挂起。
- void lockInterruptibly():线程不被中断就请求锁(Acquires the lock unless the current thread is interrupted)
- boolean trylock():尝试获取锁,如果当前该锁没有被其他线程持有,则当前线程获取该锁井返回true,否则返回false 。注意,该方法不会引起当前线程阻塞。
- boolean tryLock(long timeout, TimeUnit unit):同上,和trylock相比,多了过期时间。
- void unlock():尝试释放锁,如果当前线程持有该锁, 则调用该方法会让该线程对该线程持有的AQS状态值减l , 如果减去1后当前状态值为0 ,则当前线程会释放该锁, 否则仅仅减1而己。如果当前线程没有持有该锁而调用了该方法则会抛出IllegalMonitorStateException 异常。
例子:
3、ReentrantReadWriteLock
原理就是将读写锁分离,state高16位记录读取重入次数,低16位记录写入重入次数。以下是例子