随着学习的深入,我接触了更多之前没有接触到的知识,对线程间的同步通信有了更多的认识,之前已经学习过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()
方法。
作为一个示例,假定有一个绑定的缓冲区,它支持 put
和 take
方法。如果试图在空的缓冲区上执行 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 }