自定义显式锁BooleanLock

末蓝、 2024-03-16 12:01 107阅读 0赞

背景

synchronized关键字的缺陷







构造一个显式的BooleanLock,使其在具备synchronized关键字所有功能的同时又具备可中断和lock超时的功能。

定义Lock接口

  1. public interface Lock {
  2. void lock() throws InterruptedException;
  3. void lock(long mills) throws InterruptedException, TimeoutException;
  4. void unlock();
  5. List<Thread> getBlockedThreads();
  6. }




















在上述代码中:
·lock()方法永远阻塞,除非获取到了锁,这一点和synchronized非常类似,但是该方法是可以被中断的,中断时会抛出InterruptedException异常。
·lock(long mills)方法除了可以被中断以外,还增加了对应的超时功能。
·unlock()方法可用来进行锁的释放
·getBlockedThreads()用于获取当前有哪些线程被阻塞。

实现BooleanLock

  1. public class BooleanLock implements Lock {
  2. private Thread currentThread;
  3. private boolean locked = false;
  4. private final List<Thread> blockedList = new ArrayList<>();
  5. @Override
  6. public void lock() throws InterruptedException {
  7. synchronized (this) {
  8. //①
  9. while (locked) {
  10. //②
  11. blockedList.add(currentThread());
  12. this.wait();
  13. }
  14. blockedList.remove(currentThread());//③
  15. this.locked = true;//④
  16. this.currentThread = currentThread();//⑤
  17. }
  18. }
  19. @Override
  20. public void lock(long mills) throws InterruptedException, TimeoutException {
  21. synchronized (this) {
  22. if (mills <= 0) {
  23. //①
  24. this.lock();
  25. } else {
  26. long remainingMills = mills;
  27. long endMills = currentTimeMillis() + remainingMills;
  28. while (locked) {
  29. if (remainingMills <= 0) {
  30. //②
  31. throw new TimeoutException("can not get the lock during " + mills);
  32. }
  33. if (!blockedList.contains(currentThread())) {
  34. blockedList.add(currentThread());
  35. }
  36. this.wait(remainingMills); //③
  37. remainingMills = endMills - currentTimeMillis(); //④
  38. }
  39. blockedList.remove(currentThread()); //⑤
  40. this.locked = true;
  41. this.currentThread = currentThread();
  42. }
  43. }
  44. }
  45. @Override
  46. public void unlock() {
  47. synchronized (this) {
  48. if (currentThread == currentThread()) {
  49. //①
  50. this.locked = false; // ②
  51. Optional.of(currentThread().getName() + " release the lock.").ifPresent(System.out::println);
  52. this.notifyAll(); // ③
  53. }
  54. }
  55. }
  56. @Override
  57. public List<Thread> getBlockedThreads() {
  58. return Collections.unmodifiableList(blockedList);
  59. }
  60. }

















BooleanLock是Lock的一个Boolean实现,通过控制一个Boolean变量的开关来决定是否允许当前的线程获取该锁,首先定义三个非常重要的成员变量
其中currentThread代表当前拥有锁的线程
locked是一个boolean开关,false代表当前该锁没有被任何线程获得或者已经释放,true代表该锁已经被某个线程获得,该线程就是currentThread;
blockedList用来存储哪些线程在获取当前线程时进入了阻塞状态























在上述lock方法代码中:
①Lock()方法使用同步代码块的方式进行方法同步。
②如果当前锁已经被某个线程获得,则该线程将加入阻塞队列,并且使当前线程wait释放对this monitor的所有权。
③如果当前锁没有被其他线程获得,则该线程将尝试从阻塞队列中删除自己(注意:如果当前线程从未进入过阻塞队列,删除方法不会有任何影响;如果当前线程是从wait set中被唤醒的,则需要从阻塞队列中将自己删除)。
④locked开关被指定为true。
⑤记录获取锁的线程。























相比较lock()方法,lock(long mills)方法稍微有些复杂,我们逐步解释。在上述代码中:
①如果mills不合法,则默认调用lock()方法,当然也可以抛出参数非法的异常,一般来说,抛出异常是一种比较好的做法。
②如果remainingMills小于等于0,则意味着当前线程被其他线程唤醒或者在指定的wait时间到了之后还没有获得锁,这种情况下会抛出超时的异常。
③等待remainingMills的毫秒数,该值最开始是由其他线程传入的,但在多次wait的过程中会重新计算。
④重新计算remainingMills时间。
⑤获得该锁,并且从block列表中删除当前线程,将locked的状态修改为true并且指定获得锁的线程就是当前线程。

















unlock()方法需要做的仅仅是将locked状态修改为false,并且唤醒wait set中的其他线程,再次争抢锁资源。但是需要注意的一点是,哪个线程加的锁只能由该线程来解锁:
①判断当前线程是否为获取锁的那个线程,只有加了锁的线程才有资格进行解锁。
②将锁的locked状态修改为false。
③通知其他在wait set中的线程,你们可以再次尝试抢锁了,这里使用notify和notifyAll都可以。






至此BooleanLock的基本功能已经完成,当然读者也可以对其进行扩充,比如增加tryLock的功能,也就是说能获得锁便获得,获得不了就退出,压根不会阻塞。

使用BooleanLock

(1)多个线程通过lock()方法争抢锁

  1. public class BooleanLockTest {
  2. //定义BooleanLock
  3. private final Lock lock = new BooleanLock();
  4. //使用try..finally语句块确保lock每次都能被正确释放
  5. public void syncMethod() {
  6. try {
  7. //加锁
  8. lock.lock();
  9. int randomInt = current().nextInt(10);
  10. System.out.println(currentThread() + " get the lock.");
  11. TimeUnit.SECONDS.sleep(randomInt);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. } finally {
  15. //释放锁
  16. lock.unlock();
  17. }
  18. }
  19. public static void main(String[] args) {
  20. BooleanLockTest blt = new BooleanLockTest();
  21. //定义一个线程并且启动
  22. IntStream.range(0, 10).mapToObj(i -> new Thread(blt::syncMethod)).forEach(Thread::start);
  23. }
  24. }
  25. Thread[Thread-0,5,main] get the lock.
  26. Thread-0 release the lock.
  27. Thread[Thread-1,5,main] get the lock.
  28. Thread-1 release the lock.
  29. Thread[Thread-9,5,main] get the lock.
  30. Thread-9 release the lock.
  31. Thread[Thread-2,5,main] get the lock.
  32. Thread-2 release the lock.
  33. Thread[Thread-8,5,main] get the lock.
  34. Thread-8 release the lock.
  35. Thread[Thread-5,5,main] get the lock.
  36. Thread-5 release the lock.
  37. Thread[Thread-7,5,main] get the lock.
  38. Thread-7 release the lock.
  39. Thread[Thread-4,5,main] get the lock.
  40. Thread-4 release the lock.
  41. Thread[Thread-6,5,main] get the lock.
  42. Thread-6 release the lock.
  43. Thread[Thread-3,5,main] get the lock.
  44. Thread-3 release the lock.






根据控制台输出可以看到,每次都会确保只有一个线程能够获得锁的执行权限,这一点已经与synchronized同步非常类似。

(2)可中断被阻塞的线程

  1. BooleanLockTest blt = new BooleanLockTest();
  2. new Thread(blt::syncMethod, "T1").start();
  3. TimeUnit.MILLISECONDS.sleep(2);
  4. Thread t2 = new Thread(blt::syncMethod, "T2");
  5. t2.start();
  6. TimeUnit.MILLISECONDS.sleep(10);
  7. t2.interrupt();






运行上面的程序,在T2线程启动10毫秒以后,主动将其中断,T2线程会收到中断信号,也就是InterruptedException异常,这样也就弥补了Synchronized同步方式不可被中断的缺陷。上述程序的运行结果如下:
  1. Thread[T1,5,main] get the lock.
  2. java.lang.InterruptedException
  3. at java.base/java.lang.Object.wait(Native Method)
  4. at java.base/java.lang.Object.wait(Object.java:338)
  5. at thread.lock.customlock.BooleanLock.lock(BooleanLock.java:25)
  6. at thread.lock.customlock.BooleanLockTest.syncMethod(BooleanLockTest.java:17)
  7. at java.base/java.lang.Thread.run(Thread.java:833)
  8. T1 release the lock.






BooleanLock还存在一个问题,如果某个线程被中断,那么它将有可能还存在于blockList中,该问题的修复也非常简单,可以对BooleanLock的lock方法进行进一步的增强加以修复
  1. public void lock() throws InterruptedException {
  2. synchronized (this) {
  3. while (locked) {
  4. //暂存当前线程
  5. final Thread tempThread = currentThread();
  6. try {
  7. if (!blockedList.contains(tempThread)) {
  8. blockedList.add(tempThread);
  9. }
  10. this.wait();
  11. } catch (InterruptedException e) {
  12. //如果当前线程在wait时被中断,则从blockedList中将其删除,避免内存泄漏
  13. blockedList.remove(tempThread);
  14. //继续抛出中断异常
  15. throw e;
  16. }
  17. }
  18. blockedList.remove(currentThread());
  19. this.locked = true;
  20. this.currentThread = currentThread();
  21. }
  22. }

(3)阻塞的线程可超时







最后再写一个具有超时功能lock的使用示例,同样定义两个线程T1和T2,确保T1先执行能够最先获得锁,T2稍后启动,在1000ms以内未获得锁则会抛出超时异常
  1. public void syncMethodTimeoutable() {
  2. try {
  3. lock.lock(1000);
  4. System.out.println(currentThread() + " get the lock.");
  5. int randomInt = current().nextInt(10);
  6. TimeUnit.SECONDS.sleep(randomInt);
  7. } catch (InterruptedException | TimeoutException e) {
  8. e.printStackTrace();
  9. } finally {
  10. lock.unlock();
  11. }
  12. }
  13. BooleanLockTest blt = new BooleanLockTest();
  14. new Thread(blt::syncMethod, "T1").start();
  15. TimeUnit.MILLISECONDS.sleep(2);
  16. Thread t2 = new Thread(blt::syncMethodTimeoutable, "T2");
  17. t2.start();
  18. TimeUnit.MILLISECONDS.sleep(10);
  19. Thread[T1,5,main] get the lock.
  20. java.util.concurrent.TimeoutException: can not get the lock during 1000
  21. at thread.lock.customlock.BooleanLock.lock(BooleanLock.java:66)
  22. at thread.lock.customlock.BooleanLockTest.syncMethodTimeoutable(BooleanLockTest.java:34)
  23. at java.base/java.lang.Thread.run(Thread.java:833)
  24. T1 release the lock.

发表评论

表情:
评论列表 (有 0 条评论,107人围观)

还没有评论,来说两句吧...

相关阅读

    相关 示例

    tryLock在一定时间内获取不到锁不会一直阻塞,会返回false,然后线程会继续向下走。 package com.iteye.yuanyuan7891.thread

    相关

    显式锁:高级功能,轮询,定时,中断,公平性: 非公平发出请求时锁可用可以直接抢,否则也要排队,公平的只要锁不可用或前面有人就去排队 公平性很好,但没必要,而且会带来性能问题