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 }

猜你喜欢

转载自www.cnblogs.com/ssh-html/p/11013241.html