zoukankan      html  css  js  c++  java
  • Java中的线程--Lock和Condition实现线程同步通信

      随着学习的深入,我接触了更多之前没有接触到的知识,对线程间的同步通信有了更多的认识,之前已经学习过synchronized 实现线程间同步通信,今天来学习更多的--Lock,GO!!!

    一、初时Lock

    Lock比传统线程模型中的synchronized更加面向对象,与生活中的锁类似,锁本身也应该是一个对象,两个线程执行的代码块要实现同步互斥的效果,他们必须用同一个lock对象,锁是上在代表要操作的资源类的背部方法中,而不是线程代码中。看一下具体的代码,如何使用Lock对象:

     1 public class LockTest {
     2 
     3     public static void main(String[] args) {
     4         new LockTest().init();
     5     }
     6 
     7     private void init() {
     8         outputer outputer = new outputer();
     9         new Thread(new Runnable() {
    10             @Override
    11             public void run() {
    12                 while (true) {
    13                     try {
    14                         Thread.sleep(10);
    15                     } catch (InterruptedException e) {
    16                         e.printStackTrace();
    17                     }
    18                     outputer.output("songshengchao");
    19                 }
    20             }
    21         }).start();
    22 
    23         new Thread(new Runnable() {
    24             @Override
    25             public void run() {
    26                 while (true) {
    27                     try {
    28                         Thread.sleep(10);
    29                     } catch (InterruptedException e) {
    30                         e.printStackTrace();
    31                     }
    32                     outputer.output("dongna");
    33                 }
    34             }
    35         }).start();
    36     }
    37 
    38     static class outputer {
    39      // 创建锁对象
    40         Lock lock = new ReentrantLock();
    41 
    42         public void output(String name) {
    43             int len = name.length();
    44        // 加上锁
    45             lock.lock();
    46             try {
    47                 for (int i = 0; i < len; i++) {
    48                     System.out.print(name.charAt(i));
    49                 }
    50                 System.out.println();
    51             }finally {
    52           // 释放锁
    53                 lock.unlock();
    54             }
    55             
    56         }
    57     }

    二、读写锁

    读写锁,分为读锁和写锁,多个读锁不互斥,读锁和写锁互斥,写锁与写锁互斥,这是由JVM自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但是不能同时写,那就上读锁。如果你的代码在修改数据,只能有一个人在写,并且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁。

    看看代码中如何实现:

     1 import java.util.Random;
     2 import java.util.concurrent.locks.ReadWriteLock;
     3 import java.util.concurrent.locks.ReentrantReadWriteLock;
     4 
     5 public class ReadWriteLockTest {
     6 
     7     public static void main(String[] args) {
     8 
     9         final Queue3 q3 = new Queue3();
    10         for (int i = 0; i < 3; i++) {
    11             new Thread(new Runnable() {
    12                 @Override
    13                 public void run() {
    14                     while (true) {
    15                         q3.get();
    16                     }
    17                 }
    18             }).start();
    19 
    20             new Thread(new Runnable() {
    21                 @Override
    22                 public void run() {
    23                     while (true) {
    24                         q3.put(new Random().nextInt(10000));
    25                     }
    26 
    27                 }
    28             }).start();
    29         }
    30     }
    31 
    32     static class Queue3 {
    33 
    34         // 共享数据 只有一个线程可以写数据 多个线程读数据
    35         private Object data = null;
    36         // 读写锁对象
    37         ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    38 
    39         public void get() {
    40             // 读锁 上锁
    41             readWriteLock.readLock().lock();
    42             try {
    43                 System.out.println(Thread.currentThread().getName() + " be ready to read ");
    44                 Thread.sleep((long) (Math.random() * 1000));
    45                 System.out.println(Thread.currentThread().getName() + " have read data " + data);
    46             } catch (InterruptedException e) {
    47                 e.printStackTrace();
    48             }finally {
    49                 // 读锁 开锁
    50                 readWriteLock.readLock().unlock();
    51             }
    52 
    53         }
    54 
    55         public void put(Object data) {
    56 
    57             // 写锁 上锁
    58             readWriteLock.writeLock().lock();
    59             try {
    60                 System.out.println(Thread.currentThread().getName() + " be ready to write ");
    61                 Thread.sleep((long) (Math.random() * 1000));
    62                 this.data = data;
    63                 System.out.println(Thread.currentThread().getName() + " have write data " + data);
    64             } catch (InterruptedException e) {
    65                 e.printStackTrace();
    66             } finally {
    67                 // 写锁 开锁
    68                 readWriteLock.writeLock().unlock();
    69             }
    70         }
    71     }
    72 }

    三、缓存系统的伪代码设计

    这个jdk API文档中有一个很好的例子,就是在ReentrantReadWriteLock类中,具体可以自己看一下~ 

    主要是读写锁的实际应用,你一定要思路清晰,具体代码如何执行,都要搞清楚,注释也比较详细,哈哈!代码如下:

     1 import java.util.HashMap;
     2 import java.util.Map;
     3 import java.util.concurrent.locks.ReadWriteLock;
     4 import java.util.concurrent.locks.ReentrantReadWriteLock;
     5 
     6 public class CacheDemo {
     7 
     8     private Map<String, Object> cache = new HashMap<String, Object>();
     9     private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    10 
    11     public static void main(String[] args) {
    12 
    13     }
    14 
    15     // 这个是获取缓存中数据的方法
    16     public Object getData(String key) {
    17         readWriteLock.readLock().lock();
    18         Object value = null;
    19         try {
    20             value = cache.get(key);
    21             // 如果多个线程同时执行数据库的查询 那就要执行多次数据库查询,浪费内存 可以加synchronized 上锁,但是用读写锁是更好的方法
    22             if (value == null) {
    23                 // 这里代码就是从数据库中获取实际的相关数据,但是如果多线程的情况下呢?代码执行到这里,需要将从数据库中读取到的数据,写入到内存中
    24                 // 释放读锁
    25                 readWriteLock.readLock().unlock();
    26                 // 加上写锁,只能有一个线程进行写数据的操作
    27                 readWriteLock.writeLock().lock();
    28                 try {
    29                     // 预防多线程中同时进行写操作的线程进行数据的获取
    30                     if(value == null) {
    31                         value = "queryDB.getData()";
    32                     }
    33                 } catch (Exception e) {
    34                     e.printStackTrace();
    35                 } finally {
    36                     // 数据写进内存之后 释放写锁
    37                     readWriteLock.writeLock().unlock();
    38                 }
    39                 readWriteLock.readLock().lock();
    40             }
    41         } catch (Exception e) {
    42             e.printStackTrace();
    43         } finally {
    44             readWriteLock.readLock().unlock();
    45         }
    46         return value;
    47     }
    48 
    49 }

    四、Condition来实现线程间的通讯

     Condition的功能类似在传统线程技术中的Object的wait()和notify()的功能。在等待Condition的时候,允许“虚假唤醒”,这通常作为基础平台语义的让步。对大多数应用程序来说,这带来的实际影响很小,因为Condition总是在一个循环中被等待。并测试正在等待的状态说明

    注意:Condition是跟随Lock对象的

    JDK 中 Condition类中的例子,经典中的经典:(JDK中的解释说明)

    Condition 实例实质上被绑定到一个锁上。要为特定 Lock 实例获得 Condition 实例,请使用其 newCondition() 方法。

    作为一个示例,假定有一个绑定的缓冲区,它支持 puttake 方法。如果试图在空的缓冲区上执行 take 操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put 操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存 put 线程和 take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个 Condition 实例来做到这一点。

    代码如下:(这段代码其实挺不好理解的,为什么用两个Condition?)

     1  class BoundedBuffer {
     2    final Lock lock = new ReentrantLock();
     3    final Condition notFull  = lock.newCondition(); 
     4    final Condition notEmpty = lock.newCondition(); 
     5 
     6    final Object[] items = new Object[100];
     7    int putptr, takeptr, count;
     8 
     9    public void put(Object x) throws InterruptedException {
    10      lock.lock();
    11      try {
    12        while (count == items.length) 
    13          notFull.await();
    14        items[putptr] = x; 
    15        if (++putptr == items.length) putptr = 0;
    16        ++count;
    17        notEmpty.signal();
    18      } finally {
    19        lock.unlock();
    20      }
    21    }
    22 
    23    public Object take() throws InterruptedException {
    24      lock.lock();
    25      try {
    26        while (count == 0) 
    27          notEmpty.await();
    28        Object x = items[takeptr]; 
    29        if (++takeptr == items.length) takeptr = 0;
    30        --count;
    31        notFull.signal();
    32        return x;
    33      } finally {
    34        lock.unlock();
    35      }
    36    } 
    37  }

    终极难题,实现两个以上线程同时交替运行的代码,condition类来实现,之前写那个两个线程交替运行的时候,试着写了一下,大于2个线程如何写,但是没有写出来,今天终于解决了那个问题代码如下:

      1 public class ThreeConditionCommunication {
      2 
      3     public static void main(String[] args) {
      4         Business3 business = new Business3();
      5         // 线程2
      6         new Thread(new Runnable() {
      7 
      8             @Override
      9             public void run() {
     10                 for (int i = 1; i <= 50; i++) {
     11                     business.sub2(i);
     12                 }
     13             }
     14         }).start();
     15         
     16         // 线程3
     17         new Thread(new Runnable() {
     18 
     19             @Override
     20             public void run() {
     21                 for (int i = 1; i <= 50; i++) {
     22                     business.sub3(i);
     23                 }
     24             }
     25         }).start();
     26 
     27         // 本身main方法就是主线程,线程1 直接可以写循环代码
     28         for (int i = 1; i <= 50; i++) {
     29             business.main(i);
     30         }
     31 
     32     }
     33 
     34 }
     35 ---------------------------------------上面是测试代码----------------------------------------
     36 
     37 /**
     38  * 改造 用Condition实现三个线程间的通讯
     39  * 
     40  * @author ssc
     41  *
     42  */
     43 public class Business3 {
     44 
     45     // 是否是子线程执行 默认子线程先执行 默认主线程先执行
     46     private int shouldSub = 1;
     47     private Lock lock = new ReentrantLock();
     48     private Condition condition1 = lock.newCondition();
     49     private Condition condition2 = lock.newCondition();
     50     private Condition condition3 = lock.newCondition();
     51     
     52     public void sub2(int i) {
     53         lock.lock();
     54         try {
     55             // 不是线程2应该执行 让给线程3 线程2执行等待的方法
     56             while (shouldSub != 2) {
     57                 // this.wait();
     58                 // Condition类特有的等待方法 await() 线程2等待
     59                 condition2.await();
     60             }
     61             for (int j = 1; j <= 10; j++) {
     62                 System.out.println("sub2 thread sequece of" + j + ", loop of " + i);
     63             }
     64             // 线程2执行完毕后 让给线程3执行
     65             shouldSub = 3;
     66             // 唤醒线程3
     67             // this.notify();
     68             // 等同于 notify()
     69             condition3.signal();
     70         } catch (Exception e) {
     71             e.printStackTrace();
     72         } finally {
     73             lock.unlock();
     74         }
     75     }
     76     
     77     public void sub3(int i) {
     78         lock.lock();
     79         try {
     80             // 不是线程3应该执行 让给线程3 线程2执行等待的方法
     81             while (shouldSub != 3) {
     82                 // this.wait();
     83                 // Condition类特有的等待方法 await() 线程3等待
     84                 condition3.await();
     85             }
     86             for (int j = 1; j <= 20; j++) {
     87                 System.out.println("sub3 thread sequece of" + j + ", loop of " + i);
     88             }
     89             // 线程3执行完毕后 让给线程1 也就是主线程执行
     90             shouldSub = 1;
     91             // 唤醒线程1
     92             // this.notify();
     93             // 等同于 notify()
     94             condition1.signal();
     95         } catch (Exception e) {
     96             e.printStackTrace();
     97         } finally {
     98             lock.unlock();
     99         }
    100     }
    101 
    102     public void main(int i) {
    103         lock.lock();
    104         try {
    105             // 是线程2应该执行 让给线程2执行 主线程执行等待的方法
    106             while (shouldSub != 1) {
    107                 //this.wait();
    108                 condition1.await();
    109             }
    110             for (int j = 1; j <= 100; j++) {
    111                 System.out.println("main thread sequece of" + j + ", loop of " + i);
    112             }
    113             // 主线程执行费完毕后 交给子线程执行
    114             shouldSub = 2;
    115             // 唤醒线程2
    116             //this.notify();
    117             condition2.signal();
    118         } catch (Exception e) {
    119             e.printStackTrace();
    120         } finally {
    121             lock.unlock();
    122         }
    123     }
    124 }
  • 相关阅读:
    JZOJ 2548. 【NOIP2011模拟9.4】最大正方形
    JZOJ 3532. 【NOIP2013提高组day1】转圈游戏
    网络流模板 dinic
    1433: [ZJOI2009]假期的宿舍
    JZOJ 1285. 奶酪厂
    JZOJ 1284. 病毒
    SpringMVC路径匹配规则源码
    RESTful设计风格下SpringMVC的URI设计性能问题
    递归查询mysql数据库设计
    java定时任务调度
  • 原文地址:https://www.cnblogs.com/ssh-html/p/11013241.html
Copyright © 2011-2022 走看看