之前写了一下synchronized关键字的一点东西,那么除了synchronized可以加锁外,JUC(java.util.concurrent)提供的Lock接口也可以实现加锁解锁的功能。

看完本文,希望您可以了解或者掌握:

1:Lock接口的实现

2:Condition的原理和概念

3:ReentrantLock的实现原理,可以手写一个简单的ReentrantLock

4:ReadWriteLock的概念和实现原理,可以手写一个简单的ReadWriteLock

5:能够了解到模板模型,AQS

锁的本质:

1:加锁,其实就是加了一种权限或者说是规则。

2:获得锁,其实也就是获得了访问资源的权限。

一:Lock接口

上图是Lock接口中的一些方法,下面说下每个方法的作用:

1:lock(),lock接口是对资源进行加锁,而且加锁的时候是不死不休的,就是我加不到锁,我就一直等待着,直到我加到锁为止。

2:tryLock(),tryLock接口是尝试加锁,它就是我尝试一下去加锁,若是锁已经被占用,就不会再去等了。

3:tryLock(long time, TimeUnit unit),这个接口有个超时限制,若是锁已经被占用,就等待一段时间,时间到了后,要是锁还是被占用,就放弃。

4:lockInterruptibly(),lockInterruptibly是任人摆布的,就是说当有别的线程调用了interrupt打断它之后,它就不会再去加锁了。

下面是一个测试例子:

  1. //定义一个锁
  2. public static volatile Lock lock = new ReentrantLock();
  3.  
  4. public static void main(String[] args) throws InterruptedException {
  5.  
  6. lock.lock();
  7.  
  8. Thread thread = new Thread(new Runnable() {
  9. @Override
  10. public void run() {
  11. System.out.println("try lock start......");
  12. lock.tryLock();
  13. System.out.println("try lock end......");
  14.  
  15. System.out.println("lock start......");
  16. lock.lock();
  17. System.out.println("lock end......");
  18.  
  19. System.out.println("try lock start......");
  20. try {
  21. lock.tryLock(5, TimeUnit.SECONDS);
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. System.out.println("try lock end......");
  26.  
  27. System.out.println("try lock start......");
  28. try {
  29. lock.lockInterruptibly();
  30. } catch (InterruptedException e) {
  31. e.printStackTrace();
  32. }
  33. System.out.println("try lock end......");
  34. }
  35. });
  36. thread.start();
  37.  
  38. Thread.sleep(5000);
  39. thread.interrupt();
  40.  
  41. Thread.sleep(2000);
  42. lock.unlock();
  43. }

二:Condition

之前说了wait、notify,挂起和唤醒线程,那么Lock接口中也提供了一个Condition,Condition的底层实现机制是park和unpark,我们知道park和unpark当先唤醒后挂起时不会发生死锁,那么Condition也是一样,而且Condition中的挂起也有释放锁的语义,所以Condition在加锁或者先唤醒后挂起两种情况下都不会死锁,下面看下栗子:

  1. //定义一个锁
  2. static Lock lock = new ReentrantLock();
  3.  
  4. static Condition condition = lock.newCondition();
  5.  
  6. public static void main(String[] args) throws InterruptedException {
  7.  
  8. test1();
  9. }
  10.  
  11. public static void test1() throws InterruptedException {
  12. Thread thread = new Thread(new Runnable() {
  13. @Override
  14. public void run() {
  15. lock.lock();
  16. System.out.println("子线程获取锁。。。");
  17. try {
  18. System.out.println("子线程挂起开始。。。");
  19. condition.await();
  20. System.out.println("子线程挂起结束。。。");
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. } finally {
  24. lock.unlock();
  25. }
  26.  
  27. }
  28. });
  29. thread.start();
  30.  
  31. Thread.sleep(2000);
  32. lock.lock();
  33. System.out.println("主线程获取锁。。。");
  34. condition.signal();
  35. lock.unlock();
  36. }
  37.  
  38. public static void test2() throws InterruptedException {
  39. Thread thread = new Thread(new Runnable() {
  40. @Override
  41. public void run() {
  42. lock.lock();
  43. System.out.println("子线程获取锁。。。");
  44. try {
  45. Thread.sleep(8000);
  46. System.out.println("子线程挂起开始。。。");
  47. condition.await();
  48. System.out.println("子线程挂起结束。。。");
  49. } catch (InterruptedException e) {
  50. e.printStackTrace();
  51. } finally {
  52. lock.unlock();
  53. }
  54.  
  55. }
  56. });
  57. thread.start();
  58.  
  59. Thread.sleep(1000);
  60. lock.lock();
  61. System.out.println("主线程获取锁。。。");
  62. condition.signal();
  63. lock.unlock();
  64. }

上面测试例子中,两种情况都会执行完毕,不会死锁。

wait/notify提供的等待集合是单个的,而Condition可以提供多个等待集,下面是测试例子:

  1. public class ConditionDemo2 {
  2.  
  3. public static void main(String[] args) throws InterruptedException {
  4. ThreadQueue queue = new ThreadQueue(5);
  5.  
  6. Thread thread = new Thread(new Runnable() {
  7. @Override
  8. public void run() {
  9. for (int i=0;i<20;i++){
  10. try {
  11. queue.put("元素" + i);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. }
  16. }
  17. });
  18. thread.start();
  19.  
  20. Thread.sleep(3000);
  21. System.out.println("循环的从队列拿元素。。。");
  22. for (int i=0;i<10;i++){
  23. queue.get();
  24. Thread.sleep(3000);
  25. }
  26. }
  27.  
  28. }
  29.  
  30. //定义一个阻塞队列
  31. //put元素时,若队列已满,就阻塞,直到再有空间,未满就put元素进去
  32. //take元素时,若队列中没有元素,就阻塞,直到再有元素,有元素就直接取
  33. class ThreadQueue {
  34.  
  35. //定义一个可重入锁
  36. Lock lock = new ReentrantLock();
  37. Condition putCondition = lock.newCondition();
  38. Condition getCondition = lock.newCondition();
  39. //队列长度
  40. private volatile int length;
  41. //用来存放元素的集合
  42. List<Object> list = new ArrayList<>();
  43.  
  44. public ThreadQueue(int length) {
  45. this.length = length;
  46. }
  47.  
  48. //往队列中放元素
  49. public void put(Object obj) throws InterruptedException {
  50. lock.lock();
  51. for (;;) {
  52. //若队列还有空间,就直接放入队列,并且唤醒拿元素的等待集合,可以去拿元素了
  53. //若队列空间已经满了,就直接阻塞
  54. if (list.size() < length) {
  55. list.add(obj);
  56. System.out.println("put: " + obj);
  57. getCondition.signal();
  58. break;
  59. } else {
  60. putCondition.await();
  61. }
  62. }
  63. lock.unlock();
  64. }
  65.  
  66. //从队列中拿元素
  67. public Object get() throws InterruptedException {
  68. lock.lock();
  69. Object obj;
  70. for (;;) {
  71. //若队列中有元素就直接取,然后把取走后的元素从队列中移除,并且唤醒放元素的等待集
  72. 合,可以继续放元素了
  73. //若队列中没有元素,就阻塞等待元素
  74. if (list.size() > 0) {
  75. obj = list.get(0);
  76. list.remove(0);
  77. System.out.println("get: " + obj);
  78. putCondition.signal();
  79. break;
  80. } else {
  81. getCondition.await();
  82. }
  83. }
  84. lock.unlock();
  85. return obj;
  86. }
  87. }

三:ReentrantLock

ReentrantLock是Lock接口的一个实现,它是一个可重入锁,会有一个值去记录重入的次数。若在一个线程中加锁加了n次,那么解锁就要调用n次,如果加锁次数大于解锁次数,就不能完全释放锁,若加锁次数小于解锁次数,即解锁多调了几次,那么就会报错。下面是例子:

  1. /定义一个锁
  2. static Lock lock = new ReentrantLock();
  3.  
  4. public static void main(String[] args) {
  5.  
  6. System.out.println("here i am 1.......");
  7. lock.lock();
  8.  
  9. System.out.println("here i am 2.......");
  10. lock.lock();
  11.  
  12. System.out.println("here i am 3.......");
  13. lock.unlock();
  14.  
  15. lock.unlock();
  16.  
  17. //会报java.lang.IllegalMonitorStateException异常
  18. //lock.unlock();
  19.  
  20. new Thread(new Runnable() {
  21. @Override
  22. public void run() {
  23. lock.lock();
  24. System.out.println("子线程获取锁.......");
  25. lock.unlock();
  26. }
  27. }).start();
  28. }

下面简单实现一个可重入锁:

思考:

1. 实现可重入锁需要哪些准备呢?

2. 需要实现哪些方法呢?

那么,实现一个可重入锁,需要以下内容:

1:需要知道那个线程获取到了锁。

2:如果获取不到锁,就放入等待队列,那么就需要一个队列存放挂起的线程。

3:需要知道线程重入的次数,即需要一个变量去记录线程重入的次数。

4:需要加锁,解锁的方法

下面提供一个简单的实现,有大量注释,可以方便查阅:

  1. public class ThreadLock2 implements Lock {
  2.  
  3. //用于显示那个线程获取到锁
  4. public volatile AtomicReference<Thread> owner = new AtomicReference<>();
  5.  
  6. //用于存放没有获取到锁,挂起的线程
  7. public static BlockingDeque<Thread> waiter = new LinkedBlockingDeque<>();
  8.  
  9. //锁重入的次数
  10. volatile AtomicInteger count = new AtomicInteger(0);
  11.  
  12. //尝试加锁
  13. @Override
  14. public boolean tryLock() {
  15. int ct = count.get();
  16. //ct!=0,说明锁已被占用
  17. if (ct != 0) {
  18. //判断锁的拥有者是不是当前线程,若是,就把重入次数加一
  19. if (owner.get() == Thread.currentThread()) {
  20. count.set(ct + 1);
  21. return true;
  22. }
  23. //锁未被占用,就去抢锁
  24. } else {
  25. //CAS方式去抢锁
  26. if (count.compareAndSet(ct, ct+1)) {
  27. owner.set(Thread.currentThread());
  28. return true;
  29. }
  30. }
  31. return false;
  32. }
  33.  
  34. //加锁
  35. @Override
  36. public void lock() {
  37. if (!tryLock()) {
  38. //抢锁失败,就加入等待队列
  39. waiter.offer(Thread.currentThread());
  40. //循环的去抢锁
  41. for (;;) {
  42. //取出队列头部的线程
  43. Thread head = waiter.peek();
  44. //若队列头部的元素是当前线程,就去抢锁
  45. if (head == Thread.currentThread()) {
  46. if (tryLock()) {
  47. //抢到锁就从等待队列中取出
  48. waiter.poll();
  49. return;
  50. } else {
  51. //抢不到锁就直接挂起
  52. LockSupport.park();
  53. }
  54. } else {
  55. //不是队列头部的线程,就直接挂起
  56. LockSupport.park();
  57. }
  58. }
  59. }
  60. }
  61.  
  62. //解锁
  63. @Override
  64. public void unlock() {
  65. if (tryUnlock()) {
  66. //释放锁成功后,就从等待队列中唤醒线程
  67. Thread thread = waiter.peek();
  68. if (thread != null) {
  69. LockSupport.unpark(thread);
  70. }
  71. }
  72.  
  73. }
  74.  
  75. //尝试解锁
  76. public boolean tryUnlock() {
  77. //判断当前线程是不是锁拥有者
  78. //不是就抛出异常
  79. //是的话就释放锁
  80. if (owner.get() != Thread.currentThread()) {
  81. //测试类运行可能会进入这个错误,这和环境可能有关,但是代码的具体实现思路应该是没有
  82. //问题的
  83. throw new IllegalMonitorStateException();
  84. } else {
  85. //释放锁,就把重入次数减一
  86. int ct = count.get();
  87. int nextCt = ct - 1;
  88. count.set(nextCt);
  89. //当重入次数为0,就完全释放锁,把锁拥有者置为null
  90. if (nextCt == 0) {
  91. owner.set(null);
  92. return true;
  93. } else {
  94. return false;
  95. }
  96. }
  97. }
  98.  
  99. @Override
  100. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
  101. return false;
  102. }
  103.  
  104. @Override
  105. public void lockInterruptibly() throws InterruptedException {
  106.  
  107. }
  108.  
  109. @Override
  110. public Condition newCondition() {
  111. return null;
  112. }
  113. }

测试类:

  1. static ThreadLock2 lock = new ThreadLock2();
  2.  
  3. static volatile int i = 0;
  4.  
  5. public static void main(String[] args) throws InterruptedException {
  6. for (int i=0;i<6;i++) {
  7. new Thread(new Runnable() {
  8. @Override
  9. public void run() {
  10. for (int i=0;i<100000;i++) {
  11. add();
  12. }
  13. }
  14. }).start();
  15. }
  16. Thread.sleep(2000);
  17. System.out.println(i);
  18. }
  19.  
  20. public static void add() {
  21. lock.lock();
  22. i++;
  23. lock.unlock();
  24. }

四:synchronized和Lock的区别

4.1 synchronized

优点:

1. synchronized使用起来比较简单,语义也比较清晰。

2. JVM为synchronized提供了很多的优化,如锁消除,锁粗化,偏向锁等。

3. synchronized可以自动释放锁,可以避免死锁发生

缺点:

无法实现锁的一些高级特性,如公平锁,共享锁等。

4.2 Lock接口

优点:

synchronized的缺点就是Lock的缺点,Lock接口可以实现一些锁的高级特性。

缺点:

Lock接口需要手动的去释放锁,使用中不注意的话可能会造成死锁。

五:ReadWriteLock

ReadWriteLock读写锁,提供了一个读锁,一个写锁,读锁是共享锁,可以由多个线程持有,写锁是独占锁,只能有一个线程可以获得写锁,读写锁适用于读场景比较多的地方。

  1.  

读写锁,一个线程获取到了读锁后就不能再去获取写锁,读写是互斥的,一个线程获取到了写锁,可以锁降级为读锁,降级为读锁后,就获取不了写锁了;写锁只能由一个线程获取(写写互斥),读锁可以由多个线程共同获取,想要获取写锁,只能等到所有的读锁全部释放后,才可以去获取,想要获取读锁,也要等到写锁释放后才可以获取。

获取写锁的过程:

获取读锁的过程:

下面是实现ReadWriteLock的一个例子:

  1. public class ReadWriteLock2 {
  2.  
  3. //锁拥有者
  4. private Thread owner = null;
  5. //等待队列,存放阻塞挂起的线程
  6. private LinkedBlockingDeque<Node> waiters = new LinkedBlockingDeque<>();
  7. //读锁
  8. private AtomicInteger readCount = new AtomicInteger(0);
  9. //写锁
  10. private AtomicInteger writeCount = new AtomicInteger(0);
  11. class Node {
  12. //标识是读锁还是写锁,0为读锁,1为写锁
  13. int type = 0;
  14. //线程
  15. Thread thread = null;
  16. int arg = 0;
  17.  
  18. public Node(int type, Thread thread, int arg) {
  19. this.type = type;
  20. this.thread = thread;
  21. this.arg = arg;
  22. }
  23. }
  24.  
  25. //获取写锁,即独占锁
  26. public void lock() {
  27. int arg = 1;
  28. //尝试获取独占锁,若成功则退出,若失败继续抢锁
  29. if (!tryLock(arg)) {
  30. //抢锁失败,就放入等待队列
  31. Node node = new Node(0, Thread.currentThread(), arg);
  32. waiters.offer(node);
  33. //自旋抢锁
  34. for(;;) {
  35. Node head = waiters.peek();
  36. //判断当前线程是否在队列头部,若在就尝试抢锁,否则就挂起
  37. if (head != null && head.thread == Thread.currentThread()) {
  38. if (tryLock(arg)) {
  39. //抢锁成功就把线程从队列中移除,然后返回
  40. waiters.poll();
  41. return;
  42. } else {
  43. //抢锁失败就挂起
  44. LockSupport.park();
  45. }
  46. } else {
  47. //不是队列头部就挂起
  48. LockSupport.park();
  49. }
  50. }
  51. }
  52.  
  53. }
  54.  
  55. //尝试获取写锁,即独占锁
  56. public boolean tryLock(int arg) {
  57. //若读锁已被获取,就返回false,因为要获取写锁,要等到所有的读锁释放
  58. if (readCount.get() != 0) {
  59. return false;
  60. }
  61. int ct = writeCount.get();
  62. if (ct != 0) {
  63. //若写锁已被获取,且是当前线程获取,那么就把writeCount加1
  64. if (owner == Thread.currentThread()) {
  65. writeCount.set(ct + arg);
  66. return true;
  67. }
  68. } else {
  69. //若写锁没被获取,就用CAS方式去抢锁
  70. if (writeCount.compareAndSet(ct, ct + arg)){
  71. //抢锁成功就把当前线程赋予owner
  72. owner = Thread.currentThread();
  73. return true;
  74. }
  75. }
  76. return false;
  77. }
  78.  
  79. //释放写锁,即独占锁
  80. public boolean unLock() {
  81. int arg = 1;
  82. if (tryUnLock(arg)) {
  83. //释放锁成功,取出队列头部线程,唤醒
  84. Node headNode = waiters.peek();
  85. if (headNode != null) {
  86. LockSupport.unpark(headNode.thread);
  87. }
  88. return true;
  89. }
  90. return false;
  91. }
  92.  
  93. //尝试释放写锁,即独占锁
  94. public boolean tryUnLock(int arg) {
  95. //若锁的拥有者不是当前线程就抛异常
  96. if (owner != Thread.currentThread()) {
  97. throw new IllegalMonitorStateException();
  98. } else {
  99. //若是当前线程拥有锁,就把writeCount减一,直到writeCount为0时,完全释放锁
  100. int ct = writeCount.get();
  101. int next = ct - arg;
  102. if (next == 0) {
  103. //把owner置为null,释放锁成功
  104. owner = null;
  105. return true;
  106. } else {
  107. return false;
  108. }
  109. }
  110. }
  111.  
  112. //获取读锁,即共享锁
  113. public void sharedLock() {
  114. int arg = 1;
  115. if (trySharedLock(arg) < 0) {
  116. //抢锁失败,就放入等待队列
  117. Node node = new Node(1, Thread.currentThread(), arg);
  118. waiters.offer(node);
  119.  
  120. for (;;) {
  121. Node headNode = waiters.peek();
  122. //判断当前线程是否在队列头部,若在就尝试抢锁,否则就挂起
  123. if (headNode != null && headNode.thread == Thread.currentThread()) {
  124. if (trySharedLock(arg) >= 0) {
  125. //抢锁成功就把线程从队列中移除,然后返回
  126. waiters.poll();
  127. //若下一个线程是读锁,就把它唤醒
  128. Node next = waiters.peek();
  129. if (next != null && next.type == 0) {
  130. LockSupport.unpark(next.thread);
  131. }
  132. } else {
  133. //抢锁失败就挂起
  134. LockSupport.park();
  135. }
  136. } else {
  137. //不是队列头部就挂起
  138. LockSupport.park();
  139. }
  140. }
  141. }
  142. }
  143.  
  144. //尝试获取读锁,即共享锁
  145. public int trySharedLock(int arg) {
  146. //自旋抢锁
  147. for (;;) {
  148. //若写锁没有释放,且不是当前线程持有,就返回错误值-1,因为写锁释放后,才可以获取读锁
  149. if (writeCount.get() != 0 && owner != Thread.currentThread()) {
  150. return -1;
  151. }
  152. int ct = readCount.get();
  153. if (readCount.compareAndSet(ct, ct + arg)) {
  154. return 1;
  155. }
  156.  
  157. }
  158.  
  159. }
  160.  
  161. //尝试释放读锁,即共享锁
  162. public boolean tryUnSharedLock(int arg) {
  163. for (;;) {
  164. int ct = readCount.get();
  165. int nextCt = ct - arg;
  166. //直到readCount为0时才是完全释放锁
  167. if (readCount.compareAndSet(ct, nextCt)) {
  168. return nextCt == 0;
  169. }
  170. }
  171. }
  172.  
  173. //释放读锁,即共享锁
  174. public boolean unSharedLock() {
  175. int arg = 1;
  176. if (tryUnSharedLock(arg)) {
  177. //释放锁成功,取出队列头部线程,唤醒
  178. Node headNode = waiters.peek();
  179. if (headNode != null) {
  180. LockSupport.unpark(headNode.thread);
  181. }
  182. return true;
  183. }
  184. return false;
  185. }
  186. }

测试类:

  1. static ReadWriteLock2 lock = new ReadWriteLock2();
  2.  
  3. volatile static int i = 0;
  4.  
  5. static void add() {
  6. i++;
  7. }
  8.  
  9. public static void main(String[] args) throws InterruptedException {
  10. long startTime = System.currentTimeMillis();
  11.  
  12. for (int a=1; a<=20000; a++){
  13. final int n = a;
  14. new Thread(new Runnable() {
  15. @Override
  16. public void run() {
  17. if (n%5 ==0){
  18. lock.lock();
  19. add();
  20. lock.unLock();
  21. }else{
  22. lock.sharedLock();
  23. System.out.println("i=" +i);
  24. int a = i;
  25. lock.unSharedLock();
  26. }
  27. }
  28. }).start();
  29. }
  30.  
  31. while (true){
  32. System.out.println("目前耗时:" + (System.currentTimeMillis()-startTime) /1000 + "s");
  33. Thread.sleep(1000L);
  34. System.out.println("i=" + i);
  35.  
  36. }
  37. }

下面提供另外两个例子:改造hashMap为安全的,和实现模拟一个缓存:

  1. public class MapDemo {
  2. //将hashMap改造成线程安全的
  3. private Map<String, Object> map = new HashMap<>();
  4.  
  5. private ReadWriteLock lock = new ReentrantReadWriteLock();
  6. //初始化读锁和写锁
  7. private Lock writeLock = lock.writeLock();
  8. private Lock readLock = lock.readLock();
  9.  
  10. //根据key值获取元素
  11. public Object get(String key) {
  12. readLock.lock();
  13. try {
  14. return map.get(key);
  15. } finally {
  16. readLock.unlock();
  17. }
  18. }
  19.  
  20. //获取所有的key
  21. public Object[] allKeys() {
  22. readLock.lock();
  23. try {
  24. return map.keySet().toArray();
  25. } finally {
  26. readLock.unlock();
  27. }
  28. }
  29.  
  30. //放入元素
  31. public Object put(String key, String value) {
  32. writeLock.lock();
  33. try {
  34. return map.put(key, value);
  35. } finally {
  36. writeLock.unlock();
  37. }
  38. }
  39.  
  40. //清除所有元素
  41. public void clear() {
  42. writeLock.lock();
  43. try {
  44. map.clear();
  45. } finally {
  46. writeLock.unlock();
  47. }
  48. }
  49. }
  50. 点击并拖拽以移动
  51. //做一个缓存,从缓存中取元素
  52. public class CacheDemo {
  53.  
  54. //初始化一个读写锁
  55. private static ReadWriteLock lock = new ReentrantReadWriteLock();
  56. //用于判断缓存是否可用,是否有元素
  57. private static volatile boolean isCache;
  58.  
  59. static Object get(String key) {
  60. Object obj = null;
  61. //加读锁
  62. lock.readLock().lock();
  63. try {
  64. //若缓存可用,直接从缓存中取数据
  65. if(isCache) {
  66. obj = Cache.map.get(key);
  67. } else {
  68. //释放读锁
  69. lock.readLock().unlock();
  70. //加写锁,并不会马上获取到写锁,会等到所有读锁释放
  71. lock.writeLock().lock();
  72. try {
  73. //若缓存不可用,从数据库取数据,然后放入缓存
  74. if (!isCache) {
  75. obj = dataBaseGetData.getData();
  76. Cache.map.put(key, obj);
  77. isCache = true;
  78. }
  79. //锁降级,将写锁降级为读锁
  80. lock.readLock().lock();
  81. } finally {
  82. //释放写锁
  83. lock.writeLock().unlock();
  84. }
  85. }
  86. } finally {
  87. //释放读锁
  88. lock.readLock().unlock();
  89. }
  90. return obj;
  91. }
  92.  
  93. }
  94.  
  95. //模拟从数据库获取元素
  96. class dataBaseGetData {
  97. static String getData() {
  98. System.out.println("从数据库取元素。。。");
  99. return "name:hello,age:20";
  100. }
  101. }
  102.  
  103. //模拟缓存
  104. class Cache {
  105. static Map<String, Object> map = new HashMap<>();
  106. }

六:模板模式

模板模式就是说,有一个母版,就像PPT一样,然后把母版复制过来自己去实现自己想要的就可以了,下面是个简单的例子:

  1. //母版
  2. class mb {
  3. void title() {
  4. throw new UnsupportedOperationException();
  5. }
  6.  
  7. void content() {
  8. throw new UnsupportedOperationException();
  9. }
  10.  
  11. void foot() {
  12. throw new UnsupportedOperationException();
  13. }
  14.  
  15. public final void show() {
  16.  
  17. System.out.println("这是一个标题");
  18. title();
  19. System.out.println("字体:微软雅黑");
  20.  
  21. System.out.println("这是内容:");
  22. content();
  23. System.out.println("内容结束");
  24.  
  25. System.out.println("这是底部:");
  26. foot();
  27. }
  28. }
  29.  
  30. class PPT1 extends mb {
  31. @Override
  32. void title() {
  33. System.out.print("你好啊");
  34. }
  35.  
  36. @Override
  37. void content() {
  38. System.out.print("java。。。");
  39. System.out.print("c++");
  40. System.out.print("中国");
  41. }
  42.  
  43. @Override
  44. void foot() {
  45. System.out.print("结束了");
  46. }
  47. }

根据上面的例子改造ReentrantLock和ReadWriteLock的实现方式,先做一个模板:

  1. public class CommonLock {
  2.  
  3. //锁拥有者
  4. Thread owner = null;
  5. //等待队列,存放阻塞挂起的线程
  6. LinkedBlockingDeque<Node> waiters = new LinkedBlockingDeque<>();
  7. //读锁
  8. AtomicInteger readCount = new AtomicInteger(0);
  9. //写锁
  10. AtomicInteger writeCount = new AtomicInteger(0);
  11. class Node {
  12. //标识是读锁还是写锁,0为读锁,1为写锁
  13. int type = 0;
  14. //线程
  15. Thread thread = null;
  16. int arg = 0;
  17.  
  18. public Node(int type, Thread thread, int arg) {
  19. this.type = type;
  20. this.thread = thread;
  21. this.arg = arg;
  22. }
  23. }
  24.  
  25. //获取读锁,即独占锁
  26. public void lock() {
  27. int arg = 1;
  28. //尝试获取独占锁,若成功则退出,若失败继续抢锁
  29. if (!tryLock(arg)) {
  30. //抢锁失败,就放入等待队列
  31. Node node = new Node(0, Thread.currentThread(), arg);
  32. waiters.offer(node);
  33. //自旋抢锁
  34. for(;;) {
  35. Node head = waiters.peek();
  36. //判断当前线程是否在队列头部,若在就尝试抢锁,否则就挂起
  37. if (head != null && head.thread == Thread.currentThread()) {
  38. if (tryLock(arg)) {
  39. //抢锁成功就把线程从队列中移除,然后返回
  40. waiters.poll();
  41. return;
  42. } else {
  43. //抢锁失败就挂起
  44. LockSupport.park();
  45. }
  46. } else {
  47. //不是队列头部就挂起
  48. LockSupport.park();
  49. }
  50. }
  51. }
  52.  
  53. }
  54.  
  55. //释放写锁,即独占锁
  56. public boolean unLock() {
  57. int arg = 1;
  58. if (tryUnLock(arg)) {
  59. //释放锁成功,取出队列头部线程,唤醒
  60. Node headNode = waiters.peek();
  61. if (headNode != null) {
  62. LockSupport.unpark(headNode.thread);
  63. }
  64. return true;
  65. }
  66. return false;
  67. }
  68.  
  69. //获取读锁,即共享锁
  70. public void sharedLock() {
  71. int arg = 1;
  72. if (trySharedLock(arg) < 0) {
  73. //抢锁失败,就放入等待队列
  74. Node node = new Node(1, Thread.currentThread(), arg);
  75. waiters.offer(node);
  76.  
  77. for (;;) {
  78. Node headNode = waiters.peek();
  79. //判断当前线程是否在队列头部,若在就尝试抢锁,否则就挂起
  80. if (headNode != null && headNode.thread == Thread.currentThread()) {
  81. if (trySharedLock(arg) >= 0) {
  82. //抢锁成功就把线程从队列中移除,然后返回
  83. waiters.poll();
  84. //若下一个线程是读锁,就把它唤醒
  85. Node next = waiters.peek();
  86. if (next != null && next.type == 1) {
  87. LockSupport.unpark(next.thread);
  88. }
  89. } else {
  90. //抢锁失败就挂起
  91. LockSupport.park();
  92. }
  93. } else {
  94. //不是队列头部就挂起
  95. LockSupport.park();
  96. }
  97. }
  98. }
  99. }
  100.  
  101. //释放读锁,即共享锁
  102. public boolean unSharedLock() {
  103. int arg = 1;
  104. if (tryUnSharedLock(arg)) {
  105. //释放锁成功,取出队列头部线程,唤醒
  106. Node headNode = waiters.peek();
  107. if (headNode != null) {
  108. LockSupport.unpark(headNode.thread);
  109. }
  110. return true;
  111. }
  112. return false;
  113. }
  114.  
  115. //尝试获取写锁,即独占锁
  116. public boolean tryLock(int arg) {
  117. throw new UnsupportedOperationException();
  118. }
  119.  
  120. //尝试释放写锁,即独占锁
  121. public boolean tryUnLock(int arg) {
  122. throw new UnsupportedOperationException();
  123. }
  124.  
  125. //尝试获取读锁,即共享锁
  126. public int trySharedLock(int arg) {
  127. throw new UnsupportedOperationException();
  128.  
  129. }
  130.  
  131. //尝试释放读锁,即共享锁
  132. public boolean tryUnSharedLock(int arg) {
  133. throw new UnsupportedOperationException();
  134. }
  135.  
  136. }

改造后的ReentrantLock:

  1. //private Thread owner = null;
  2.  
  3. //锁拥有者
  4. volatile AtomicReference<Thread> owner = new AtomicReference<>();
  5.  
  6. //等待队列
  7. private LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();
  8.  
  9. //记录重入的次数
  10. volatile AtomicInteger count = new AtomicInteger(0);
  11.  
  12. CommonLock lock = new CommonLock(){
  13. //尝试获取写锁,即独占锁
  14. @Override
  15. public boolean tryLock(int arg) {
  16. //若读锁已被获取,就返回false,因为要获取写锁,要等到所有的读锁释放
  17. if (lock.readCount.get() != 0) {
  18. return false;
  19. }
  20. int ct = lock.writeCount.get();
  21. if (ct != 0) {
  22. //若写锁已被获取,且是当前线程获取,那么就把writeCount加1
  23. if (lock.owner == Thread.currentThread()) {
  24. lock.writeCount.set(ct + arg);
  25. return true;
  26. }
  27. } else {
  28. //若写锁没被获取,就用CAS方式去抢锁
  29. if (lock.writeCount.compareAndSet(ct, ct + arg)){
  30. //抢锁成功就把当前线程赋予owner
  31. lock.owner = Thread.currentThread();
  32. return true;
  33. }
  34. }
  35. return false;
  36. }
  37.  
  38. //尝试释放写锁,即独占锁
  39. @Override
  40. public boolean tryUnLock(int arg) {
  41. //若锁的拥有者不是当前线程就抛异常
  42. if (lock.owner != Thread.currentThread()) {
  43. throw new IllegalMonitorStateException();
  44. } else {
  45. //若是当前线程拥有锁,就把writeCount减一,直到writeCount为0时,完全释放锁
  46. int ct = lock.writeCount.get();
  47. int next = ct - arg;
  48. if (next == 0) {
  49. //把owner置为null,释放锁成功
  50. lock.owner = null;
  51. return true;
  52. } else {
  53. return false;
  54. }
  55. }
  56. }
  57. };
  58.  
  59. @Override
  60. public void lock() {
  61. lock.lock();
  62. }
  63.  
  64. @Override
  65. public void unlock() {
  66. lock.unLock();
  67. }
  68.  
  69. @Override
  70. public void lockInterruptibly() throws InterruptedException {
  71.  
  72. }
  73.  
  74. @Override
  75. public boolean tryLock() {
  76. return lock.tryLock(1);
  77. }
  78.  
  79. public boolean tryUnlock() {
  80. return lock.tryUnLock(1);
  81. }
  82.  
  83. @Override
  84. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
  85. return false;
  86. }
  87.  
  88. @Override
  89. public Condition newCondition() {
  90. return null;
  91. }

改造后的ReadWriteLock:

  1. public class ReadWriteLock2 implements ReadWriteLock {
  2.  
  3. CommonLock lock = new CommonLock(){
  4. //尝试获取写锁,即独占锁
  5. @Override
  6. public boolean tryLock(int arg) {
  7. //若读锁已被获取,就返回false,因为要获取写锁,要等到所有的读锁释放
  8. if (lock.readCount.get() != 0) {
  9. return false;
  10. }
  11. int ct = lock.writeCount.get();
  12. if (ct != 0) {
  13. //若写锁已被获取,且是当前线程获取,那么就把writeCount加1
  14. if (lock.owner == Thread.currentThread()) {
  15. lock.writeCount.set(ct + arg);
  16. return true;
  17. }
  18. } else {
  19. //若写锁没被获取,就用CAS方式去抢锁
  20. if (lock.writeCount.compareAndSet(ct, ct + arg)){
  21. //抢锁成功就把当前线程赋予owner
  22. lock.owner = Thread.currentThread();
  23. return true;
  24. }
  25. }
  26. return false;
  27. }
  28.  
  29. //尝试释放写锁,即独占锁
  30. @Override
  31. public boolean tryUnLock(int arg) {
  32. //若锁的拥有者不是当前线程就抛异常
  33. if (lock.owner != Thread.currentThread()) {
  34. throw new IllegalMonitorStateException();
  35. } else {
  36. //若是当前线程拥有锁,就把writeCount减一,直到writeCount为0时,完全释放锁
  37. int ct = lock.writeCount.get();
  38. int next = ct - arg;
  39. if (next == 0) {
  40. //把owner置为null,释放锁成功
  41. lock.owner = null;
  42. return true;
  43. } else {
  44. return false;
  45. }
  46. }
  47. }
  48.  
  49. //尝试获取读锁,即共享锁
  50. @Override
  51. public int trySharedLock(int arg) {
  52. //自旋抢锁
  53. for (;;) {
  54. //若写锁没有释放,且不是当前线程持有,就返回错误值-1,因为写锁释放后,才可以获取读锁
  55. if (writeCount.get() != 0 && owner != Thread.currentThread()) {
  56. return -1;
  57. }
  58. int ct = readCount.get();
  59. if (readCount.compareAndSet(ct, ct + arg)) {
  60. return 1;
  61. }
  62.  
  63. }
  64.  
  65. }
  66.  
  67. //尝试释放读锁,即共享锁
  68. @Override
  69. public boolean tryUnSharedLock(int arg) {
  70. for (;;) {
  71. int ct = readCount.get();
  72. int nextCt = ct - arg;
  73. //直到readCount为0时才是完全释放锁
  74. if (readCount.compareAndSet(ct, nextCt)) {
  75. return nextCt == 0;
  76. }
  77. }
  78. }
  79. };
  80.  
  81. @Override
  82. public Lock readLock() {
  83. return new Lock() {
  84.  
  85. @Override
  86. public void lock() {
  87. lock.sharedLock();
  88. }
  89.  
  90. @Override
  91. public void lockInterruptibly() throws InterruptedException {
  92.  
  93. }
  94.  
  95. @Override
  96. public boolean tryLock() {
  97. return lock.trySharedLock(0) == 1;
  98. }
  99.  
  100. @Override
  101. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
  102. return false;
  103. }
  104.  
  105. @Override
  106. public void unlock() {
  107. lock.tryUnSharedLock(0);
  108. }
  109.  
  110. @Override
  111. public Condition newCondition() {
  112. return null;
  113. }
  114. };
  115. }
  116.  
  117. @Override
  118. public Lock writeLock() {
  119. return new Lock() {
  120. @Override
  121. public void lock() {
  122. lock.lock();
  123. }
  124.  
  125. @Override
  126. public void lockInterruptibly() throws InterruptedException {
  127.  
  128. }
  129.  
  130. @Override
  131. public boolean tryLock() {
  132. return lock.tryLock(1);
  133. }
  134.  
  135. @Override
  136. public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
  137. return false;
  138. }
  139.  
  140. @Override
  141. public void unlock() {
  142. lock.unLock();
  143. }
  144.  
  145. @Override
  146. public Condition newCondition() {
  147. return null;
  148. }
  149. };
  150. }
  151. }

七:AQS抽象队列同步器

JUC包就是基于AQS实现的,AQS的设计模式就是模板模式,数据结构是双向链表和锁状态(state),底层实现是CAS。它的state不像上面读写锁说的是两个count,它用一个state实现,即我们知道int类型有8个字节,它的实现是前4个字节可以标识读锁,后四个字节可以标识写锁。

AQS本文不做详细解释。。。

好了到此,Lock接口的相关内容已经结束了。

多线程之Lock接口的更多相关文章

  1. 多线程之Lock的基本介绍

    基本介绍 java.util.concurrent.locks是java1.5之后出现的一种锁实现方式,是一个接口.但是在这之前已经有一个同步机制的实现就是synchronized关键字,那为什么还要 ...

  2. 多线程之Lock

    Java并发编程:Lock 在上一篇文章中我们讲到了如何使用关键字synchronized来实现同步访问.本文我们继续来探讨这个问题,从Java 5之后,在java.util.concurrent.l ...

  3. Java——多线程之Lock锁

    Java多线系列文章是Java多线程的详解介绍,对多线程还不熟悉的同学可以先去看一下我的这篇博客Java基础系列3:多线程超详细总结,这篇博客从宏观层面介绍了多线程的整体概况,接下来的几篇文章是对多线 ...

  4. Java多线程之Callable接口与Runnable的实现以及选择

    通过实现Runnable接口的实现 package Thread; import java.util.concurrent.ExecutorService;import java.util.concu ...

  5. Java多线程之Lock的使用

    import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util ...

  6. java多线程之Lock线程同步

    1.线程同步: package cn.itcast.heima2; import java.util.concurrent.locks.Lock; import java.util.concurren ...

  7. Java多线程之Lock的使用(转)

    package thread.lock; import java.util.concurrent.ExecutorService; import java.util.concurrent.Execut ...

  8. (转)Java多线程之Lock的使用 (待整理)

    import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util ...

  9. Java多线程之Callable接口的实现

    Callable 和 Future接口  Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务. Callable和Runn ...

随机推荐

  1. ImageApparate(幻影)镜像加速服务让镜像分发效率提升 5-10 倍

    作者介绍 李昂,腾讯高级开发工程师,主要关注容器存储和镜像存储相关领域,目前主要负责腾讯容器镜像服务和镜像存储加速系统的研发和设计工作. 李志宇,腾讯云后台开发工程师.负责腾讯云 TKE 集群节点和运 ...

  2. SpringBoot Test 多线程报错:dataSource already closed

    1:前言 最近在项目中使用多线程对大任务拆分处理时,进行数据库操作的时候报错了. 业务代码大概是这样的: @Service public calss TestServiceImpl implement ...

  3. 使用 xunit 编写测试代码

    使用 xunit 编写测试代码 Intro xunit 是 .NET 里使用非常广泛的一个测试框架,有很多测试项目都是在使用 xunit 作为测试框架,不仅仅有很多开源项目在使用,很多微软的项目也在使 ...

  4. ADT基础(二)—— Tree,Heap and Graph

    ADT基础(二)-- Tree,Heap and Graph 1 Tree(二叉树) 先根遍历 (若二叉树为空,则退出,否则进行下面操作) 访问根节点 先根遍历左子树 先根遍历右子树 退出 访问顺序为 ...

  5. RabbitMQ(三) SpringBoot2.x 集成 RabbitMQ

    3-1 RabbitMQ 整合 SpringBoot2.x 生产者发送消息 创建 SpringBoot 项目application.properties 配置 spring.rabbitmq.host ...

  6. JUC-ThreadLocal

    目录 ThreadLocal ThreadLocal测试 ThreadLocal类结构 前言 多线程访问同一个共享变量的时候也别容易出现并发问题,特别是在多线程需要对一个共享变量进行写入的时候.为了保 ...

  7. Pyqt5实现model/View,解决tableView出现空白行问题。

    项目中表格需要显示5万条数据以上,并且实时刷新.开始使用的tableWidget,数据量一大显得力不从心,所以使用Qt的Model/View来重新实现.下面是更改之前编写的小Demo. import ...

  8. java基础知识 + 常见面试题

    准备校招面试之Java篇 一. Java SE 部分 1.1 Java基础 1. 请你解释Object若不重写hashCode()的话,hashCode()如何计算出来的? Object 的 hash ...

  9. CCF(通信网络):简单DFS+floyd算法

    通信网络 201709-4 一看到题目分析了题意之后,我就想到用floyd算法来求解每一对顶点的最短路.如果一个点和任意一个点都有最短路(不为INF),那么这就是符合的一个答案.可是因为题目超时,只能 ...

  10. Docker 三剑客 到 k8s 介绍

    一.Docker 三剑客 Docker-Compose Docker-Compose 是用来管理你的容器的,有点像一个容器的管家,想象一下当你的Docker中有成百上千的容器需要启动,如果一个一个的启 ...