1. 程式人生 > >每天一例多執行緒[day20]-----ReentrantLock

每天一例多執行緒[day20]-----ReentrantLock

ReentrantLock是JDK的併發包下locks包下的工具類,ReentrantLock比synchonized效能要好,但是jdk1.8之後synchonized的效能也得到大幅度提升,不亞於ReentrantLock。

鎖的演變

 ReentrantLock是由synchonized和物件鎖演變而來。回憶以下之前使用synchronized和Object鎖的方式:

  1. public void method1(){

  2. synchronized (this) { //物件鎖

  3. try {

  4. System.out.println("do method1..");

  5. Thread.sleep(2000);

  6. } catch (InterruptedException e) {

  7. e.printStackTrace();

  8. }

  9. }

  10. }

  1. public void method2(){ //類鎖

  2. synchronized (ObjectLock.class) {

  3. try {

  4. System.out.println("do method2..");

  5. Thread.sleep(2000);

  6. } catch (InterruptedException e) {

  7. e.printStackTrace();

  8. }

  9. }

  10. }

  1. private Object lock = new Object();

  2. public void method3(){ //任何物件鎖

  3. synchronized (lock) {

  4. try {

  5. System.out.println("do method3..");

  6. Thread.sleep(2000);

  7. } catch (InterruptedException e) {

  8. e.printStackTrace();

  9. }

  10. }

  11. }

而ReentrantLock的使用則靈活很多:

  1. import java.util.concurrent.locks.Lock;

  2. import java.util.concurrent.locks.ReentrantLock;

  3. public class UseReentrantLock {

  4. private Lock lock = new ReentrantLock();

  5. public void method1(){

  6. try {

  7. lock.lock();

  8. System.out.println("當前執行緒:" + Thread.currentThread().getName() + "進入method1..");

  9. Thread.sleep(1000);

  10. System.out.println("當前執行緒:" + Thread.currentThread().getName() + "退出method1..");

  11. Thread.sleep(1000);

  12. } catch (InterruptedException e) {

  13. e.printStackTrace();

  14. } finally {

  15. lock.unlock();

  16. }

  17. }

  18. public void method2(){

  19. try {

  20. lock.lock();

  21. System.out.println("當前執行緒:" + Thread.currentThread().getName() + "進入method2..");

  22. Thread.sleep(2000);

  23. System.out.println("當前執行緒:" + Thread.currentThread().getName() + "退出method2..");

  24. Thread.sleep(1000);

  25. } catch (InterruptedException e) {

  26. e.printStackTrace();

  27. } finally {

  28. lock.unlock();

  29. }

  30. }

  31. public static void main(String[] args) {

  32. final UseReentrantLock ur = new UseReentrantLock();

  33. Thread t1 = new Thread(new Runnable() {

  34. @Override

  35. public void run() {

  36. ur.method1();

  37. ur.method2();

  38. }

  39. }, "t1");

  40. t1.start();

  41. try {

  42. Thread.sleep(10);

  43. } catch (InterruptedException e) {

  44. e.printStackTrace();

  45. }

  46. //System.out.println(ur.lock.getQueueLength());

  47. }

  48. }

列印:

  1. 當前執行緒:t1進入method1..

  2. 當前執行緒:t1退出method1..

  3. 當前執行緒:t1進入method2..

  4. 當前執行緒:t1退出method2..

分析:我們定義了一個執行緒t1,在t1中執行method1和method2兩個方法,這兩個方法都加了ReentrantLock,那麼在執行時如果method1先獲得lock,就會執行method1的方法,unlock釋放鎖後,才能進入method2再次獲得鎖並執行method2,然後釋放鎖。

鎖的通訊

Object物件的wait和notify必須配合synchonized使用。而ReentrantLock配合Condition使用。

  1. import java.util.concurrent.locks.Condition;

  2. import java.util.concurrent.locks.Lock;

  3. import java.util.concurrent.locks.ReentrantLock;

  4. public class UseCondition {

  5. private Lock lock = new ReentrantLock();

  6. private Condition condition = lock.newCondition();

  7. public void method1(){

  8. try {

  9. lock.lock();

  10. System.out.println("當前執行緒:" + Thread.currentThread().getName() + "進入等待狀態..");

  11. Thread.sleep(3000);

  12. System.out.println("當前執行緒:" + Thread.currentThread().getName() + "釋放鎖..");

  13. condition.await(); //阻塞並釋放鎖     類似於 Object wait

  14. System.out.println("當前執行緒:" + Thread.currentThread().getName() +"繼續執行...");

  15. } catch (Exception e) {

  16. e.printStackTrace();

  17. } finally {

  18. lock.unlock();

  19. }

  20. }

  21. public void method2(){

  22. try {

  23. lock.lock();

  24. System.out.println("當前執行緒:" + Thread.currentThread().getName() + "進入..");

  25. Thread.sleep(3000);

  26. System.out.println("當前執行緒:" + Thread.currentThread().getName() + "發出喚醒..");

  27. condition.signal(); //喚醒 不釋放鎖  類似於Object notify

  28. } catch (Exception e) {

  29. e.printStackTrace();

  30. } finally {

  31. lock.unlock();

  32. }

  33. }

  34. public static void main(String[] args) {

  35. final UseCondition uc = new UseCondition();

  36. Thread t1 = new Thread(new Runnable() {

  37. @Override

  38. public void run() {

  39. uc.method1();

  40. }

  41. }, "t1");

  42. Thread t2 = new Thread(new Runnable() {

  43. @Override

  44. public void run() {

  45. uc.method2();

  46. }

  47. }, "t2");

  48. t1.start();

  49. t2.start();

  50. }

  51. }

列印:

  1. 當前執行緒:t1進入等待狀態..

  2. 當前執行緒:t1釋放鎖..

  3. 當前執行緒:t2進入..

  4. 當前執行緒:t2發出喚醒..

  5. 當前執行緒:t1繼續執行...

分析:我們定義t1和t2兩個執行緒,t1執行method1,t2執行method2,t1進入method1後獲得lock,進入等待,3s後呼叫condition.await();釋放鎖,進入阻塞狀態。然後t2執行緒獲得lock,3s後發出喚醒condition.signal();接著t1被喚醒,列印“繼續執行...”

多Condition

我們可以通過1個lock物件產生多個Condition進行多執行緒之間的互動,很靈活。可以使得喚醒部分執行緒,而其他執行緒繼續等待通知。

  1. import java.util.concurrent.locks.Condition;

  2. import java.util.concurrent.locks.ReentrantLock;

  3. public class UseManyCondition {

  4. private ReentrantLock lock = new ReentrantLock();

  5. private Condition c1 = lock.newCondition();

  6. private Condition c2 = lock.newCondition();

  7. public void m1(){

  8. try {

  9. lock.lock();

  10. System.out.println("當前執行緒:" +Thread.currentThread().getName() + "進入方法m1等待..");

  11. c1.await();

  12. System.out.println("當前執行緒:" +Thread.currentThread().getName() + "方法m1繼續..");

  13. } catch (Exception e) {

  14. e.printStackTrace();

  15. } finally {

  16. lock.unlock();

  17. }

  18. }

  19. public void m2(){

  20. try {

  21. lock.lock();

  22. System.out.println("當前執行緒:" +Thread.currentThread().getName() + "進入方法m2等待..");

  23. c1.await();

  24. System.out.println("當前執行緒:" +Thread.currentThread().getName() + "方法m2繼續..");

  25. } catch (Exception e) {

  26. e.printStackTrace();

  27. } finally {

  28. lock.unlock();

  29. }

  30. }

  31. public void m3(){

  32. try {

  33. lock.lock();

  34. System.out.println("當前執行緒:" +Thread.currentThread().getName() + "進入方法m3等待..");

  35. c2.await();

  36. System.out.println("當前執行緒:" +Thread.currentThread().getName() + "方法m3繼續..");

  37. } catch (Exception e) {

  38. e.printStackTrace();

  39. } finally {

  40. lock.unlock();

  41. }

  42. }

  43. public void m4(){

  44. try {

  45. lock.lock();

  46. System.out.println("當前執行緒:" +Thread.currentThread().getName() + "喚醒..");

  47. c1.signalAll();

  48. } catch (Exception e) {

  49. e.printStackTrace();

  50. } finally {

  51. lock.unlock();

  52. }

  53. }

  54. public void m5(){

  55. try {

  56. lock.lock();

  57. System.out.println("當前執行緒:" +Thread.currentThread().getName() + "喚醒..");

  58. c2.signal();

  59. } catch (Exception e) {

  60. e.printStackTrace();

  61. } finally {

  62. lock.unlock();

  63. }

  64. }

  65. public static void main(String[] args) {

  66. final UseManyCondition umc = new UseManyCondition();

  67. Thread t1 = new Thread(new Runnable() {

  68. @Override

  69. public void run() {

  70. umc.m1();

  71. }

  72. },"t1");

  73. Thread t2 = new Thread(new Runnable() {

  74. @Override

  75. public void run() {

  76. umc.m2();

  77. }

  78. },"t2");

  79. Thread t3 = new Thread(new Runnable() {

  80. @Override

  81. public void run() {

  82. umc.m3();

  83. }

  84. },"t3");

  85. Thread t4 = new Thread(new Runnable() {

  86. @Override

  87. public void run() {

  88. umc.m4();

  89. }

  90. },"t4");

  91. Thread t5 = new Thread(new Runnable() {

  92. @Override

  93. public void run() {

  94. umc.m5();

  95. }

  96. },"t5");

  97. t1.start(); // c1先阻塞

  98. t2.start(); // c1先阻塞

  99. t3.start(); // c2先阻塞

  100. try {

  101. Thread.sleep(2000);

  102. } catch (InterruptedException e) {

  103. e.printStackTrace();

  104. }

  105. t4.start(); // 喚醒 c1

  106. try {

  107. Thread.sleep(2000);

  108. } catch (InterruptedException e) {

  109. e.printStackTrace();

  110. }

  111. t5.start(); // 喚醒c2

  112. }

  113. }

分析:定義5個執行緒:t1、t2、t3、t4、t5,分別執行m1、m2、m3、m4、m5這五個方法。m1和m2都阻塞了c1這個Condition,而m3阻塞的是c2這個Condition,m4中喚醒了c1,m5喚醒了c2,t1和t2跟t3先執行,那麼執行緒t1和t2跟t3都分別被c1和c2阻塞,直到t4喚醒了c1,t1和t2繼續執行,t5喚醒了t2,t3繼續執行。

列印:

  1. 當前執行緒:t1進入方法m1等待..

  2. 當前執行緒:t2進入方法m2等待..

  3. 當前執行緒:t3進入方法m3等待..

  4. 當前執行緒:t4喚醒..

  5. 當前執行緒:t1方法m1繼續..

  6. 當前執行緒:t2方法m2繼續..

  7. 當前執行緒:t5喚醒..

  8. 當前執行緒:t3方法m3繼續..

 瞭解:ReentrantLock預設非公平,效能比公平鎖要高