高并发编程之ReentrantLock

  上文学习jvm提供的同步方法synchronized的用法,一些常见的业务类型以及一道以前阿里的面试题,从中学习到了一些并发编程的一些规则以及建议,本文主要学习jdk提供的同步方法reentrantLock。

一、ReentrantLock关键字

  reentrantLock是JDK提供的一款同步手工锁,可重入锁。

   reentrantLock可以完成synchronized做的同样的功能,但是需要手工释放锁,使用synchronized的时候遇到异常jvm会自动释放锁,但是reentrantLock不会自动释放,需要手动去释放锁,所以一般是将释放锁写到finally里面的。

  我们看下面代码:

 

 1 /**
 2  * reentrantLock可以完成synchronized做的同样的功能,但是需要手工释放锁,
 3  * 使用synchronized的时候遇到异常jvm会自动释放锁,但是reentrantLock不会自动释放,
 4  * 需要手动去释放锁,所以一般是将释放锁写到finally里面的。
 5  * @author Wuyouxin
 6  *
 7  */
 8 public class ReentrantLock1 {
 9     Lock lock = new ReentrantLock();
10     void m1 (){
11         try {
12             lock.lock();//相当于synchronized(this)
13             for (int i = 0; i < 10; i++) {
14                 TimeUnit.SECONDS.sleep(1);
15                 System.out.println(i);
16             }
17         } catch (InterruptedException e) {
18             e.printStackTrace();
19         } finally {
20             lock.unlock();//释放锁
21         }
22     }
23     
24     void m2 (){
25         //如果想要两个方法互斥,则锁定同一把锁即可
26         lock.lock();
27         System.out.println("m2 ...");
28         lock.unlock();
29     }
30     
31     public static void main(String[] args) {
32         final ReentrantLock1 r1 = new ReentrantLock1();
33         new Thread(new Runnable() {
34             
35             @Override
36             public void run() {
37                 r1.m1();
38             }
39         }, "t1").start();
40         
41         try {
42             TimeUnit.SECONDS.sleep(1);
43         } catch (InterruptedException e) {
44             e.printStackTrace();
45         }
46         
47         new Thread(new Runnable() {
48             
49             @Override
50             public void run() {
51                 r1.m2();
52             }
53         }, "t2").start();
54     }
55 }

 

二、tryLock方法

  在使用reentrantLock时还可以进行尝试性锁定“tryLock”,这样会去判断是否可以锁定,或者指定时间内是否可以锁定,线程可以决定是否继续等待。

  我们看下面代码:

  1 /**
  2  * 在使用reentrantLock时还可以进行尝试性锁定“tryLock”,这样会去判断是否可以锁定,
  3  * 或者指定时间内是否可以锁定,线程可以决定是否继续等待。
  4  * @author Wuyouxin
  5  *
  6  */
  7 public class ReentrantLock2 {
  8     Lock lock = new ReentrantLock();
  9     
 10     void m1 (){
 11         try {
 12             lock.lock();
 13             for (int i = 0; i < 10; i++) {
 14                 TimeUnit.SECONDS.sleep(1);
 15                 System.out.println(i);
 16             }
 17         } catch (Exception e) {
 18             e.printStackTrace();
 19         }
 20     }
 21     
 22     /**
 23      * 使用tryLock锁定尝试锁定,不管是否锁定,方法都将继续执行
 24      * 也可以根据tryLock的返回值来判断是否锁定
 25      */
 26     void m2 (){
 27         boolean b = lock.tryLock();
 28         try {
 29             if (b){
 30                 System.out.println("m2已经锁定");
 31                 //已经锁定的业务逻辑
 32             } else {
 33                 System.out.println("m2没有锁定");
 34                 //没有锁定的业务逻辑
 35             }
 36         } catch (Exception e) {
 37             e.printStackTrace();
 38         } finally {
 39             if (b){
 40                 lock.unlock();
 41             }
 42         }
 43     }
 44     
 45     /**
 46      * 如果在5秒内没有锁定对象则继续进行
 47      */
 48     void m3 (){
 49         boolean b = false;
 50         try {
 51             b = lock.tryLock(5, TimeUnit.SECONDS);
 52             if (b){
 53                 System.out.println("m3已经锁定");
 54                 //已经锁定的业务逻辑
 55             } else {
 56                 System.out.println("m3没有锁定");
 57                 //没有锁定的业务逻辑
 58             }
 59         } catch (Exception e) {
 60             e.printStackTrace();
 61         } finally {
 62             if (b){
 63                 lock.unlock();
 64             }
 65         }
 66     }
 67     
 68     public static void main(String[] args) {
 69         final ReentrantLock2 r2 = new ReentrantLock2();
 70         
 71         new Thread(new Runnable() {
 72             
 73             @Override
 74             public void run() {
 75                 r2.m1();
 76             }
 77         }, "t1").start();
 78         
 79         try {
 80             TimeUnit.SECONDS.sleep(1);
 81         } catch (InterruptedException e) {
 82             e.printStackTrace();
 83         }
 84         
 85         new Thread(new Runnable() {
 86             
 87             @Override
 88             public void run() {
 89                 r2.m2();
 90             }
 91         }, "t2").start();
 92         
 93         try {
 94             TimeUnit.SECONDS.sleep(1);
 95         } catch (InterruptedException e) {
 96             e.printStackTrace();
 97         }
 98         
 99         new Thread(new Runnable() {
100             
101             @Override
102             public void run() {
103                 r2.m3();
104             }
105         }, "t3").start();
106     }
107 }

 

三、lockInterruptibly方法

  使用ReentrantLock还可以使用lockInterruptibly方法可以对interrupt做出响应,在一个线程等待锁的过程中可以被打断。

  我们看下面代码:

 1 /**
 2  * 使用ReentrantLock还可以使用lockInterruptibly方法可以对interrupt做出响应,
 3  * 在一个线程等待锁的过程中可以被打断。
 4  * @author Wuyouxin
 5  *
 6  */
 7 public class ReentrantLock3 {
 8 
 9     public static void main(String[] args) {
10         final Lock lock = new ReentrantLock();
11         
12         new Thread(new Runnable() {
13             
14             @Override
15             public void run() {
16                 try {
17                     lock.lock();
18                     System.out.println("t1 start");
19                     TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
20                     System.out.println("t1 end");
21                 } catch (Exception e) {
22                     e.printStackTrace();
23                 } finally {
24                     lock.unlock();
25                 }
26             }
27         }, "t1").start();
28         
29         Thread t2 = new Thread(new Runnable() {
30             
31             @Override
32             public void run() {
33                 boolean b = false;
34                 try {
35                     lock.lockInterruptibly();
36                     b = true;
37                     System.out.println("t2 start");
38                     TimeUnit.SECONDS.sleep(5);
39                     System.out.println("t2 end");
40                 } catch (InterruptedException e) {
41                     System.out.println("Interrupt!");
42                     e.printStackTrace();
43                 } finally {
44                     System.out.println("unlock");
45                     if (b){
46                         lock.unlock();
47                     }
48                 }
49             }
50         }, "t2");
51         t2.start();
52         
53         try {
54             TimeUnit.SECONDS.sleep(3);
55         } catch (InterruptedException e) {
56             e.printStackTrace();
57         }
58         t2.interrupt();
59     }
60 }

  上面代码由于t2线程在调用interrupt方法时没有获取到资源,所以由主线程可以直接打断t2线程。

 

四、ReentrantLock公平锁

  公平锁:当一个线程池运行结束之后其他线程获得锁是公平的先等待先得到。所以叫做公平锁。

  非公平锁:当一个线程池运行结束之后其他线程获得锁是随机的,所以叫非公平锁,也叫竞争锁。

  ReentrantLock还可以指定为公平锁。

  我们看下面代码:

 1 **
 2  * ReentrantLock还可以设置公平锁
 3  * @author Wuyouxin
 4  *
 5  */
 6 public class ReentrantLock4 extends Thread{
 7     //默认为非公平锁,true为公平锁
 8     private static ReentrantLock lock = new ReentrantLock(true);
 9     public void run (){
10         for (int i = 0; i < 100; i++) {
11             lock.lock();
12             try {
13                 System.out.println(Thread.currentThread().getName() + "获得锁");
14             } catch (Exception e){
15                 e.printStackTrace();
16             } finally{
17                 lock.unlock();
18             }
19         }
20     }
21     
22     public static void main(String[] args) {
23         ReentrantLock4 r4 = new ReentrantLock4();
24         new Thread(r4, "t1").start();
25         new Thread(r4, "t2").start();
26     }
27 }

 

  面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法,能够支持2个生产者线程和10个消费者线程的阻塞调用。

  使用synchronized 的 wait 和 notify 来实现:

 1 public class MyContainer1<T> {
 2 
 3     private final LinkedList<T> lists = new LinkedList<T>(); 
 4     private final static int MAX = 10;
 5     private int count = 0;
 6     
 7     public int getCount (){
 8         return this.count;
 9     }
10     
11     public synchronized void put (T t){
12         //这里为什么使用while而不是if,因为如果是if,
13         //当线程被唤醒后,其他线程先一步put进去,if不会再继续判断,
14         //而是直接往下走这时再add则会超出范围
15         while (lists.size() == MAX){
16             try {
17                 this.wait();
18             } catch (InterruptedException e) {
19                 e.printStackTrace();
20             }
21         }
22         lists.add(t);
23         count++;
24         this.notifyAll();//通知消费者线程进行消费
25     }
26     
27     public synchronized T get(){
28         T t = null;
29         while (lists.size() == 0){
30             try {
31                 this.wait();
32             } catch (InterruptedException e) {
33                 e.printStackTrace();
34             }
35         }
36         t = lists.removeFirst();
37         count--;
38         this.notifyAll();//通知生产者线程进行生产
39         return t;
40     }
41     
42     
43     public static void main(String[] args) {
44         final MyContainer1<String> container = new MyContainer1<String>();
45         for (int i = 0; i < 10; i++) {
46             //消费者线程
47             new Thread(new Runnable() {
48                 @Override
49                 public void run() {
50                     for (int j=0;j<5;j++)System.out.println(container.get());
51                 }
52             }, "c消费者:" + i).start();
53         }
54         
55         try {
56             TimeUnit.SECONDS.sleep(2);
57         } catch (InterruptedException e) {
58             e.printStackTrace();
59         }
60         for (int i = 0; i < 2; i++) {
61             //生产者线程
62             new Thread(new Runnable() {
63                 @Override
64                 public void run() {
65                     for (int j = 0; j < 25; j++) {
66                         container.put(Thread.currentThread().getName());
67                     }    
68                 }
69             }, "p生产者:" + i).start();
70         }
71         
72     
73     }
74 }

  这里为什么使用while而不是if,因为如果是if,当线程被唤醒后,其他线程先一步put进去,if不会再继续判断,而是直接往下走这时再add则会超出范围。

  使用Lock 和 Condition实现,可以精确的唤醒某些线程:

 1 /**
 2  * 使用lock和 Condition实现,Condition方法可以精确的唤醒某些线程。
 3  * @author Wuyouxin
 4  *
 5  */
 6 public class MyContainer2<T> {
 7 
 8     final private LinkedList<T> list = new LinkedList<T>();
 9     final private static int MAX = 10;
10     private int conut = 0;
11     
12     private Lock lock = new ReentrantLock();
13     
14     private Condition producer = lock.newCondition();//生产者锁
15     private Condition consumer = lock.newCondition();//消费者锁
16     
17     public int getCount (){
18         return this.conut;
19     }
20     
21     public void put (T t){
22         try {
23             lock.lock();
24             while (this.list.size() == MAX){
25                 producer.await();
26             }
27             this.list.add(t);
28             this.conut++;
29             this.consumer.signalAll();//通知消费者线程开始消费
30         } catch (Exception e) {
31             e.printStackTrace();
32         } finally {
33             lock.unlock();
34         }
35     }
36     
37     public T get(){
38         T t = null;
39         try {
40             lock.lock();
41             while (this.list.size() == 0){
42                 consumer.await();
43             }
44             t = this.list.removeFirst();
45             this.conut--;
46             producer.signalAll();
47         } catch (Exception e) {
48             e.printStackTrace();
49         } finally {
50             lock.unlock();
51         } 
52         return t;
53     }
54     
55     public static void main(String[] args) {
56         final MyContainer2<String> c = new MyContainer2<String>();
57         
58         for (int i = 0; i < 10; i++) {
59             new Thread(new Runnable() {
60                 
61                 @Override
62                 public void run() {
63                     while(true){
64                         System.out.println(c.get());
65                     }
66                 }
67             }, "c" + i).start();
68         }
69         
70         for (int i = 0; i < 2; i++) {
71             new Thread(new Runnable() {
72                 
73                 @Override
74                 public void run() {
75                     while(true){
76                         c.put(Thread.currentThread().getName());
77                     }
78                 }
79             }, "p" + i).start();
80             
81         }
82     }
83     
84 }

 

五、ThreadLocal(线程局部变量)

  ThreadLocal是空间转换时间,synchronized是时间转换空间,比如Hibernate中的session就存在ThreadLocal中,避免synchronized使用。

 1 /**
 2  * ThreadLock线程局部变量
 3  * 
 4  * ThreadLocal是空间转换时间,synchronized是时间转换空间,
 5  * 比如Hibernate中的session就存在ThreadLocal中,避免synchronized使用。
 6  * @author Wuyouxin
 7  *
 8  */
 9 public class ThreadLocal1 {
10     static ThreadLocal<Person> t1 = new ThreadLocal<Person>();
11     
12     public static void main(String[] args) {
13         new Thread(new Runnable() {
14             
15             @Override
16             public void run() {
17                 try {
18                     TimeUnit.SECONDS.sleep(5);
19                 } catch (InterruptedException e) {
20                     e.printStackTrace();
21                 }
22                 System.out.println(t1.get());
23             }
24         }, "t1").start();
25         
26         new Thread(new Runnable() {
27             
28             @Override
29             public void run() {
30                 t1.set(new Person());
31             }
32         }, "t2").start();
33     }
34 }
35 
36 class Person {
37     String name = "zhangsan";
38 }

  按理来说t1对象是同一个,第二个线程往里面set了一个对象,第一个线程应该可以get到,但是ThreadLocal不可以,他相当于线程的局部变量,不可以被其他线程获取。

 

posted on 2018-04-15 15:25  Herrt灬凌夜  阅读(286)  评论(0编辑  收藏  举报

导航