标签:语义 return set node lte override linked tor enc
之前写了一下synchronized关键字的一点东西,那么除了synchronized可以加锁外,JUC(java.util.concurrent)提供的Lock接口也可以实现加锁解锁的功能。
看完本文,希望您可以了解或者掌握:
1:Lock接口的实现
2:Condition的原理和概念
3:ReentrantLock的实现原理,可以手写一个简单的ReentrantLock
4:ReadWriteLock的概念和实现原理,可以手写一个简单的ReadWriteLock
5:能够了解到模板模型,AQS
锁的本质:
1:加锁,其实就是加了一种权限或者说是规则。
2:获得锁,其实也就是获得了访问资源的权限。
上图是Lock接口中的一些方法,下面说下每个方法的作用:
1:lock(),lock接口是对资源进行加锁,而且加锁的时候是不死不休的,就是我加不到锁,我就一直等待着,直到我加到锁为止。
2:tryLock(),tryLock接口是尝试加锁,它就是我尝试一下去加锁,若是锁已经被占用,就不会再去等了。
3:tryLock(long time, TimeUnit unit),这个接口有个超时限制,若是锁已经被占用,就等待一段时间,时间到了后,要是锁还是被占用,就放弃。
4:lockInterruptibly(),lockInterruptibly是任人摆布的,就是说当有别的线程调用了interrupt打断它之后,它就不会再去加锁了。
下面是一个测试例子:
//定义一个锁 public static volatile Lock lock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { lock.lock(); Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println("try lock start......"); lock.tryLock(); System.out.println("try lock end......"); System.out.println("lock start......"); lock.lock(); System.out.println("lock end......"); System.out.println("try lock start......"); try { lock.tryLock(5, TimeUnit.SECONDS); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("try lock end......"); System.out.println("try lock start......"); try { lock.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("try lock end......"); } }); thread.start(); Thread.sleep(5000); thread.interrupt(); Thread.sleep(2000); lock.unlock(); }
之前说了wait、notify,挂起和唤醒线程,那么Lock接口中也提供了一个Condition,Condition的底层实现机制是park和unpark,我们知道park和unpark当先唤醒后挂起时不会发生死锁,那么Condition也是一样,而且Condition中的挂起也有释放锁的语义,所以Condition在加锁或者先唤醒后挂起两种情况下都不会死锁,下面看下栗子:
上面测试例子中,两种情况都会执行完毕,不会死锁。
wait/notify提供的等待集合是单个的,而Condition可以提供多个等待集,下面是测试例子:
public class ConditionDemo2 { public static void main(String[] args) throws InterruptedException { ThreadQueue queue = new ThreadQueue(5); Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i=0;i<20;i++){ try { queue.put("元素" + i); } catch (InterruptedException e) { e.printStackTrace(); } } } }); thread.start(); Thread.sleep(3000); System.out.println("循环的从队列拿元素。。。"); for (int i=0;i<10;i++){ queue.get(); Thread.sleep(3000); } } } //定义一个阻塞队列 //put元素时,若队列已满,就阻塞,直到再有空间,未满就put元素进去 //take元素时,若队列中没有元素,就阻塞,直到再有元素,有元素就直接取 class ThreadQueue { //定义一个可重入锁 Lock lock = new ReentrantLock(); Condition putCondition = lock.newCondition(); Condition getCondition = lock.newCondition(); //队列长度 private volatile int length; //用来存放元素的集合 List<Object> list = new ArrayList<>(); public ThreadQueue(int length) { this.length = length; } //往队列中放元素 public void put(Object obj) throws InterruptedException { lock.lock(); for (;;) { //若队列还有空间,就直接放入队列,并且唤醒拿元素的等待集合,可以去拿元素了 //若队列空间已经满了,就直接阻塞 if (list.size() < length) { list.add(obj); System.out.println("put: " + obj); getCondition.signal(); break; } else { putCondition.await(); } } lock.unlock(); } //从队列中拿元素 public Object get() throws InterruptedException { lock.lock(); Object obj; for (;;) { //若队列中有元素就直接取,然后把取走后的元素从队列中移除,并且唤醒放元素的等待集 合,可以继续放元素了 //若队列中没有元素,就阻塞等待元素 if (list.size() > 0) { obj = list.get(0); list.remove(0); System.out.println("get: " + obj); putCondition.signal(); break; } else { getCondition.await(); } } lock.unlock(); return obj; } }
ReentrantLock是Lock接口的一个实现,它是一个可重入锁,会有一个值去记录重入的次数。若在一个线程中加锁加了n次,那么解锁就要调用n次,如果加锁次数大于解锁次数,就不能完全释放锁,若加锁次数小于解锁次数,即解锁多调了几次,那么就会报错。下面是例子:
下面简单实现一个可重入锁:
思考:
1. 实现可重入锁需要哪些准备呢?
2. 需要实现哪些方法呢?
那么,实现一个可重入锁,需要以下内容:
1:需要知道那个线程获取到了锁。
2:如果获取不到锁,就放入等待队列,那么就需要一个队列存放挂起的线程。
3:需要知道线程重入的次数,即需要一个变量去记录线程重入的次数。
4:需要加锁,解锁的方法
下面提供一个简单的实现,有大量注释,可以方便查阅:
测试类:
4.1 synchronized
优点:
1. synchronized使用起来比较简单,语义也比较清晰。
2. JVM为synchronized提供了很多的优化,如锁消除,锁粗化,偏向锁等。
3. synchronized可以自动释放锁,可以避免死锁发生
缺点:
无法实现锁的一些高级特性,如公平锁,共享锁等。
4.2 Lock接口
优点:
synchronized的缺点就是Lock的缺点,Lock接口可以实现一些锁的高级特性。
缺点:
Lock接口需要手动的去释放锁,使用中不注意的话可能会造成死锁。
ReadWriteLock读写锁,提供了一个读锁,一个写锁,读锁是共享锁,可以由多个线程持有,写锁是独占锁,只能有一个线程可以获得写锁,读写锁适用于读场景比较多的地方。
读写锁,一个线程获取到了读锁后就不能再去获取写锁,读写是互斥的,一个线程获取到了写锁,可以锁降级为读锁,降级为读锁后,就获取不了写锁了;写锁只能由一个线程获取(写写互斥),读锁可以由多个线程共同获取,想要获取写锁,只能等到所有的读锁全部释放后,才可以去获取,想要获取读锁,也要等到写锁释放后才可以获取。
获取写锁的过程:
获取读锁的过程:
下面是实现ReadWriteLock的一个例子:
测试类:
下面提供另外两个例子:改造hashMap为安全的,和实现模拟一个缓存:
模板模式就是说,有一个母版,就像PPT一样,然后把母版复制过来自己去实现自己想要的就可以了,下面是个简单的例子:
根据上面的例子改造ReentrantLock和ReadWriteLock的实现方式,先做一个模板:
改造后的ReentrantLock:
改造后的ReadWriteLock:
JUC包就是基于AQS实现的,AQS的设计模式就是模板模式,数据结构是双向链表和锁状态(state),底层实现是CAS。它的state不像上面读写锁说的是两个count,它用一个state实现,即我们知道int类型有8个字节,它的实现是前4个字节可以标识读锁,后四个字节可以标识写锁。
AQS本文不做详细解释。。。
好了到此,Lock接口的相关内容已经结束了。
标签:语义 return set node lte override linked tor enc
原文地址:https://www.cnblogs.com/fox2said/p/14667244.html