• 高并发编程之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不可以,他相当于线程的局部变量,不可以被其他线程获取。

  • 相关阅读:
    javascritp对fckeditor编辑器赋值取值
    [.net]发布基于C#与Flash通信的游戏设计器完整项目及源代码下载
    Sun VirtualBox更新到3.0.8.53138
    JavaScript强弹代码
    给Repeater、Datalist和Datagrid增加自动编号列
    Adobe发布新版Photoshop.com 支持iPhone
    网站免费提交搜索引擎入口地址?
    利用FMS做在线视频录制
    Apache Lucene 2.9的改进
    KDE 4.3.2 发布
  • 原文地址:https://www.cnblogs.com/wuyx/p/8781334.html
Copyright © 2020-2023  润新知