ReentrantLock、Condition及ReadWriteLock
先看一下ReentrantLock和Condition
-
以一个简单的ReentrantLock和Condition的例子为入口
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class ConditionDemo { public static void main(String[] args) { final ReentrantLock lock = new ReentrantLock(); final Condition condition = lock.newCondition(); Thread thread1 = new Thread(new Runnable() { public void run() { try { lock.lock(); // Thread1首先获得锁,然后调用condition.await()等待信号 System.out.println("Thread1 get lock, then wait for new signal"); condition.await(); // 当Thread2中调用condition.signal()/signalAll()之后,Thread1被唤醒继续执行 System.out.println("Thread1 got new signal and continue..."); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); System.out.println("Thread1 unlock"); } } }); thread1.start(); Thread thread2 = new Thread(new Runnable() { public void run() { lock.lock(); // Thread2获得锁 System.out.println("Thread2 get lock, then send a signal."); // 调用signalAll()通知所有等待状态的线程 condition.signalAll();// 此时Thread1被唤醒,并开始争夺锁,但是由于thread2还没有释放锁,因此无法继续运行,thread1继续阻塞 System.out.println("Thread2 sent a signal and continue"); try { Thread.sleep(1000);// 等待1秒钟后再释放锁 } catch (InterruptedException e) { e.printStackTrace(); } lock.unlock(); // Thread2释放锁之后Thread1重新获得锁继续运行 System.out.println("Thread2 unlock"); } }); thread2.start(); } }
-
执行结果
Thread1 get lock, then wait for new signal Thread2 get lock, then send a signal. Thread2 sent a signal and continue Thread2 unlock Thread1 got new signal and continue... Thread1 unlock
从上面的结果可以看出,thread1执行之后首先获得了锁,但是随后执行了condition.await()之后又释放了锁进入等待状态。然后thread2执行之后获得锁,再发送signal给thread1,但此时还没有释放锁,所以thread1依旧阻塞直到一秒后thread2释放锁thread1才能继续运行。
-
因此Condition非常适合线程间的通信,下面以消费者生产者的例子来说明展示Condition的强大
地主家的傻儿子和慢慢收租的地主
-
银行卡
public class BankCard { private Long balance = 1000000L; public BankCard(Long balance) { this.balance = balance; } public BankCard() { } public Long getBalance() { return balance; } public void setBalance(Long balance) { this.balance = balance; } }
-
傻儿子
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; public class StupidSon implements Runnable { private BankCard bankCard; private int id; private Lock lock; private Condition condition; public StupidSon(BankCard bankCard, int id, Lock lock, Condition condition) { this.bankCard = bankCard; this.id = id; this.lock = lock; this.condition = condition; } public void run() { try { while (true) { lock.lock(); // 傻儿子每次消费1000,当余额不足时等老父亲赚钱存到银行卡 while (bankCard.getBalance() - 1000 <= 0) { System.out.println("StupidSon" + id + ", $" + bankCard.getBalance() + " no more money, wait for father's signal.."); condition.await(); } bankCard.setBalance(bankCard.getBalance() - 1000); System.out.println("StupidSon" + id + " cost $1000, remains:" + bankCard.getBalance()); lock.unlock(); // 一秒钟消费一次 Thread.sleep(1000L); } } catch (Exception e) { e.printStackTrace(); lock.unlock(); } } }
-
老父亲
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; public class HardFather implements Runnable { private BankCard bankCard; private Lock lock; private Condition condition; public HardFather(BankCard bankCard, Lock lock, Condition condition) { this.bankCard = bankCard; this.lock = lock; this.condition = condition; } public void run() { while (true) { lock.lock(); bankCard.setBalance(bankCard.getBalance() + 500); System.out.println("Father earn $500, remains:" + bankCard.getBalance()); if (bankCard.getBalance() > 1000 && bankCard.getBalance() < 2000) { // 当金额超过1000之后告诉傻儿子们可以来拿钱了 condition.signalAll(); System.out.println("Money above 1000 again, sent signal."); } lock.unlock(); try { // 三秒存一次钱 Thread.sleep(3000); } catch (Exception e) { e.printStackTrace(); } } } }
-
测试实例
public class Main { public static void main(String[] args) { // 初始化金额为1万 BankCard bankCard = new BankCard(10000L); Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); // 这里创建两个儿子 StupidSon son1 = new StupidSon(bankCard, 1, lock, condition); StupidSon son2 = new StupidSon(bankCard, 2, lock, condition); // 一个父亲,公用一个lock和同一个condition HardFather father = new HardFather(bankCard, lock, condition); // 放入线程池执行 ExecutorService pool = Executors.newCachedThreadPool(); pool.submit(father); pool.submit(son1); pool.submit(son2); } }
-
执行结果
Father earn $500, remains:10500 StupidSon1 cost $1000, remains:9500 StupidSon2 cost $1000, remains:8500 StupidSon1 cost $1000, remains:7500 StupidSon2 cost $1000, remains:6500 StupidSon1 cost $1000, remains:5500 StupidSon2 cost $1000, remains:4500 Father earn $500, remains:5000 StupidSon1 cost $1000, remains:4000 StupidSon2 cost $1000, remains:3000 StupidSon1 cost $1000, remains:2000 StupidSon2 cost $1000, remains:1000 StupidSon1, $1000 no more money, wait for father's signal.. StupidSon2, $1000 no more money, wait for father's signal.. Father earn $500, remains:1500 Money above 1000 again, sent signal. StupidSon1 cost $1000, remains:500 StupidSon2, $500 no more money, wait for father's signal.. StupidSon1, $500 no more money, wait for father's signal.. Father earn $500, remains:1000 Father earn $500, remains:1500 Money above 1000 again, sent signal. StupidSon2 cost $1000, remains:500 StupidSon1, $500 no more money, wait for father's signal.. StupidSon2, $500 no more money, wait for father's signal.. Father earn $500, remains:1000 Father earn $500, remains:1500 Money above 1000 again, sent signal. StupidSon1 cost $1000, remains:500 StupidSon2, $500 no more money, wait for father's signal..
从结果可以看出,当金额少于1000之后两个儿子都在等待父亲的信号,然后当他们的父亲存钱达到1500之后发送信号,这两个儿子便开始争夺锁然后消耗金额继续等待。这其实就是最基本的一个生产者消费者问题,使用ReentrantLock和Conditon之后处理生产者消费者问题就变得非常简单。
下面来看一下读写锁ReadWriteLock
银行卡使用上例中的就不贴代码了
-
傻儿子如下
import java.util.concurrent.locks.ReadWriteLock; public class StupidSon implements Runnable { private ReadWriteLock lock; private int id; private BankCard bankCard; public StupidSon(ReadWriteLock lock, BankCard bankCard, int id) { this.lock = lock; this.bankCard = bankCard; this.id = id; } public void run() { int cost = 1000; while (true) { lock.writeLock().lock(); // 获取写锁,读锁和写锁是互斥、写锁和写锁也是互斥的,此时其他线程读、写锁都将无法获取只能等待 if (bankCard.getBalance() - cost < 0) { System.out.println("StupidSon" + id + " no more money"); lock.writeLock().unlock(); // 判断金额不足之后释放写锁,跳出while循环结束线程 break; } bankCard.setBalance(bankCard.getBalance() - cost); System.out.println("StupidSon" + id + " cost $" + cost + ", remain:" + bankCard.getBalance()); try { // 等待500毫秒之后再释放写锁 System.out.print("StupidSon" + id + " wait for 500ms..."); Thread.sleep(500); // 修改金额之后释放写锁,此时其他线程可以继续竞争获取写锁和读锁 lock.writeLock().unlock(); System.out.println("StupidSon" + id + "release writeLock."); } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("StupidSon" + id + " exit."); } }
-
父母类 起到监督金额的作用
import java.util.concurrent.locks.ReadWriteLock; public class Parents implements Runnable { private ReadWriteLock lock; private BankCard bankCard; private int type; public Parents(ReadWriteLock lock, BankCard bankCard, int type) { this.lock = lock; this.bankCard = bankCard; this.type = type; } public void run() { String mof = type == 1 ? "Mother" : "Father"; while (true) { lock.readLock().lock(); // 获取读锁, 读锁和读锁是不互斥的 也就是说多个线程可以同时获取读锁,但是读写锁是互斥的,此时写锁无法被获得只能等待 if (bankCard.getBalance() <= 0) { System.out.println(mof + " saw no more money. stop check!"); lock.readLock().unlock();// 这里一定要释放读锁,否则会导致死锁 break; } System.out.println(mof + " check balance:" + bankCard.getBalance()); try { // 等待500毫秒之后再释放读锁 System.out.print(mof + " wait for 500ms..."); Thread.sleep(500); System.out.println(mof + " release readLock."); } catch (InterruptedException e) { e.printStackTrace(); } // 读取结束后释放读锁,写锁可以被其他线程竞争获取到 lock.readLock().unlock(); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(mof+" exit"); } }
-
测试
public class Main { public static void main(String[] args) { ReadWriteLock lock = new ReentrantReadWriteLock(); BankCard bankCard = new BankCard(10000L); // 初始化四个儿子和两老 Runnable son = new StupidSon(lock, bankCard, 1); Runnable son2 = new StupidSon(lock, bankCard, 2); Runnable son3 = new StupidSon(lock, bankCard, 3); Runnable son4 = new StupidSon(lock, bankCard, 4); Runnable mother = new Parents(lock, bankCard, 1); Runnable father = new Parents(lock, bankCard, 2); ExecutorService threadPoolExecutor = Executors.newCachedThreadPool(); threadPoolExecutor.submit(son3); threadPoolExecutor.submit(son4); threadPoolExecutor.submit(son2); threadPoolExecutor.submit(mother); threadPoolExecutor.submit(father); threadPoolExecutor.submit(son); } }
-
运行结果
StupidSon4 cost $1000, remain:9000 StupidSon4 wait for 500ms...StupidSon4release writeLock. StupidSon2 cost $1000, remain:8000 StupidSon2 wait for 500ms...StupidSon2release writeLock. StupidSon3 cost $1000, remain:7000 StupidSon3 wait for 500ms...StupidSon3release writeLock. Mother check balance:7000 // 从这里可以看出读锁不互斥,因为母亲未释放读锁的时候父亲依旧可以获取读锁 Mother wait for 500ms...Father check balance:7000 Father wait for 500ms...Father release readLock. Mother release readLock. StupidSon1 cost $1000, remain:6000 StupidSon1 wait for 500ms...StupidSon1release writeLock. StupidSon4 cost $1000, remain:5000 StupidSon4 wait for 500ms...StupidSon4release writeLock. StupidSon2 cost $1000, remain:4000 StupidSon2 wait for 500ms...StupidSon2release writeLock. StupidSon3 cost $1000, remain:3000 StupidSon3 wait for 500ms...StupidSon3release writeLock. StupidSon4 cost $1000, remain:2000 StupidSon4 wait for 500ms...StupidSon4release writeLock. StupidSon2 cost $1000, remain:1000 StupidSon2 wait for 500ms...StupidSon2release writeLock. Mother check balance:1000 Mother wait for 500ms...Father check balance:1000 Father wait for 500ms...Father release readLock. Mother release readLock. StupidSon1 cost $1000, remain:0 StupidSon1 wait for 500ms...StupidSon1release writeLock. StupidSon3 no more money StupidSon3 exit. StupidSon4 no more money StupidSon4 exit. StupidSon2 no more money StupidSon2 exit. Father saw no more money. stop check! Father exit Mother saw no more money. stop check! Mother exit StupidSon1 no more money StupidSon1 exit.
由上面的结果可以很直观的了解到读写锁互斥、写锁也写锁之间互斥、读锁和读锁之间不互斥。
读写锁的使用时一定要注意的是要及时释放锁,否则容易导致死锁的发生,最保险的方式是将unlock代码放在finally代码块中去