Java并发编程 - 锁Lock取款机场景实例
转自:http://www.verejava.com/?id=17236703718463
锁Lock取款机场景实例
Lock是一个接口,ReentrantLock是它的实现类,下面通过“取款机案例”来剖析它的4个常用方法。
1.爸爸妈妈同时在ATM上登录取款(不加任何锁)
public class Bank { private static double money = 10000; public void login(Thread currentUserThread) { System.out.println(Thread.currentThread().getName() + " 登录进入银行" + " 当前银行余额 : " + money); } public void logout() { System.out.println(Thread.currentThread().getName() + " 退出银行"); } public double withdraw(double withdrawMoney) { if (this.money < withdrawMoney) { System.out.println(Thread.currentThread().getName() + " 当前银行余额 : " + this.money + " 余额不够"); return 0; } this.money -= withdrawMoney; System.out.println(Thread.currentThread().getName() + " 取款 : " + withdrawMoney + " 当前银行余额 : " + this.money); return withdrawMoney; } }
public class TestLock { public static void main(String[] args) { final Bank bank = new Bank(); //启动爸爸线程 Thread fatherThread = new Thread("爸爸") { public void run() { try { //爸爸登录 bank.login(Thread.currentThread()); //过2秒取10000 Thread.sleep(2000); bank.withdraw(10000); } catch (InterruptedException e) { e.printStackTrace(); } } }; fatherThread.start(); // 启动妈妈线程 Thread motherThread = new Thread("妈妈") { public void run() { try { //妈妈登录 bank.login(Thread.currentThread()); //过5秒取 10000 Thread.sleep(5000); bank.withdraw(10000); } catch (InterruptedException e) { e.printStackTrace(); } } }; motherThread.start(); } }
结果:
妈妈 登录进入银行 当前银行余额 : 10000.0 爸爸 登录进入银行 当前银行余额 : 10000.0 爸爸 取款 : 10000.0 当前银行余额 : 0.0 妈妈 当前银行余额 : 0.0 余额不够
妈妈登录显示银行余额为10000,但是当她取钱时却显示“余额不足”,产生了数据不一致。
2.同一时刻,爸爸或妈妈线程只能有一个能够登录银行取款(获取lock()),另外一个线程需要等待, 直到unlock()释放锁
public class Bank { private static double money = 10000; private Lock lock = new ReentrantLock(); public void login(Thread currentUserThread) { lock.lock();//登录加锁 System.out.println(Thread.currentThread().getName() + " 登录进入银行" + " 当前银行余额 : " + money); } public void logout() { lock.unlock();//退出释放锁 System.out.println(Thread.currentThread().getName() + " 退出银行"); } public double withdraw(double withdrawMoney) { if (this.money < withdrawMoney) { System.out.println(Thread.currentThread().getName() + " 当前银行余额 : " + this.money + " 余额不够"); return 0; } this.money -= withdrawMoney; System.out.println(Thread.currentThread().getName() + " 取款 : " + withdrawMoney + " 当前银行余额 : " + this.money); return withdrawMoney; } }
public class TestLock { public static void main(String[] args) { final Bank bank = new Bank(); //启动爸爸线程 Thread fatherThread = new Thread("爸爸") { public void run() { try { //爸爸登录 bank.login(Thread.currentThread()); //过2秒取10000 Thread.sleep(2000); bank.withdraw(10000); //爸爸退出 bank.logout(); } catch (InterruptedException e) { e.printStackTrace(); } } }; fatherThread.start(); // 启动妈妈线程 Thread motherThread = new Thread("妈妈") { public void run() { try { //妈妈登录 bank.login(Thread.currentThread()); //过5秒取 10000 Thread.sleep(5000); bank.withdraw(10000); //妈妈退出 bank.logout(); } catch (InterruptedException e) { e.printStackTrace(); } } }; motherThread.start(); } }
结果:
爸爸 登录进入银行 当前银行余额 : 10000.0 爸爸 取款 : 10000.0 当前银行余额 : 0.0 爸爸 退出银行 妈妈 登录进入银行 当前银行余额 : 0.0 妈妈 当前银行余额 : 0.0 余额不够 妈妈 退出银行
lock() 和unlock()成对出现,在login(Thread currentUserThread) 登录方法中调用 lock() ,在 logout()退出方法中调用了unlock()。
也就是说,Lock类的锁机制允许在不同的方法中加锁和解锁,而synchronized关键字只能在同一个方法中加锁和解锁。
3. 通过tryLock()判断是否可以获得锁, 能获得锁返回true,否则返回false
public class Bank { private static double money = 10000; private Lock lock = new ReentrantLock(); public void login(Thread currentUserThread) { //判断是否已经有线程登录 if (!lock.tryLock()) { System.out.println(Thread.currentThread().getName() + " 有人已经登录进入银行 请稍等"); } else { System.out.println(Thread.currentThread().getName() + " 登录进入银行" + " 当前银行余额: " + money); } } public void logout() { lock.unlock();//退出释放锁 System.out.println(Thread.currentThread().getName() + " 退出银行"); } public double withdraw(double withdrawMoney) { if (this.money < withdrawMoney) { System.out.println(Thread.currentThread().getName() + " 当前银行余额: " + this.money + " 余额不够"); return 0; } this.money -= withdrawMoney; System.out.println(Thread.currentThread().getName() + " 取款: " + withdrawMoney + " 当前银行余额: " + this.money); return withdrawMoney; } }
public class TestLock { public static void main(String[] args) { final Bank bank = new Bank(); //启动爸爸线程 Thread fatherThread = new Thread("爸爸") { public void run() { try { //爸爸登录 bank.login(Thread.currentThread()); //过2秒取10000 Thread.sleep(2000); bank.withdraw(10000); //爸爸退出 bank.logout(); } catch (InterruptedException e) { e.printStackTrace(); } } }; fatherThread.start(); // 启动妈妈线程 Thread motherThread = new Thread("妈妈") { public void run() { //妈妈登录 bank.login(Thread.currentThread()); } }; motherThread.start(); } }
结果:
妈妈 有人已经登录进入银行 请稍等 爸爸 登录进入银行 当前银行余额: 10000.0 爸爸 取款: 10000.0 当前银行余额: 0.0 爸爸 退出银行
4. 通过tryLock(long time,TimeUnit timeUnit)设置一段时间后重新进入
public class Bank { private static double money = 10000; private Lock lock = new ReentrantLock(); public void login(Thread currentUserThread) { //如果登录不成功,10秒后再重新尝试获得锁 try { if (!lock.tryLock(10, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName() + " 有人已经登录进入银行,请稍等"); } else { System.out.println(Thread.currentThread().getName() + " 登录进入银行" + " 当前银行余额: " + money); } } catch (InterruptedException e) { e.printStackTrace(); } } public void logout() { lock.unlock();//退出释放锁 System.out.println(Thread.currentThread().getName() + " 退出银行"); } public double withdraw(double withdrawMoney) { if (this.money < withdrawMoney) { System.out.println(Thread.currentThread().getName() + " 当前银行余额: " + this.money + " 余额不够"); return 0; } this.money -= withdrawMoney; System.out.println(Thread.currentThread().getName() + " 取款: " + withdrawMoney + " 当前银行余额: " + this.money); return withdrawMoney; } }
public class TestLock { public static void main(String[] args) { final Bank bank = new Bank(); //启动爸爸线程 Thread fatherThread = new Thread("爸爸") { public void run() { try { //爸爸登录 bank.login(Thread.currentThread()); //过2秒取10000 Thread.sleep(2000); bank.withdraw(10000); //爸爸退出 bank.logout(); } catch (InterruptedException e) { e.printStackTrace(); } } }; fatherThread.start(); // 启动妈妈线程 Thread motherThread = new Thread("妈妈") { public void run() { //妈妈登录 bank.login(Thread.currentThread()); } }; motherThread.start(); } }
结果妈妈10秒后重新尝试得到锁登录进入了银行 :
爸爸 登录进入银行 当前银行余额: 10000.0 爸爸 取款: 10000.0 当前银行余额: 0.0 爸爸 退出银行 妈妈 登录进入银行 当前银行余额: 0.0
5.如果爸爸线程调用 lock() 获得锁以后,没有unlock()释放锁,妈妈线程将会一直等待产生死锁,即使调用 interrupt()中断也没有作用。
public class Bank { private static double money = 10000; private Lock lock = new ReentrantLock(); public void login(Thread currentUserThread) { lock.lock(); System.out.println(Thread.currentThread().getName() + " 登录进入银行" + " 当前银行余额 : " + money); } public void logout() { lock.unlock();//退出释放锁 System.out.println(Thread.currentThread().getName() + " 退出银行"); } public double withdraw(double withdrawMoney) { if (this.money < withdrawMoney) { System.out.println(Thread.currentThread().getName() + " 当前银行余额: " + this.money + " 余额不够"); return 0; } this.money -= withdrawMoney; System.out.println(Thread.currentThread().getName() + " 取款: " + withdrawMoney + " 当前银行余额: " + this.money); return withdrawMoney; } }
public class TestLock { public static void main(String[] args) { final Bank bank = new Bank(); //启动爸爸线程 Thread fatherThread = new Thread("爸爸") { public void run() { try { //爸爸登录 bank.login(Thread.currentThread()); //过2秒取10000 Thread.sleep(2000); bank.withdraw(10000); } catch (InterruptedException e) { e.printStackTrace(); } } }; fatherThread.start(); // 启动妈妈线程 Thread motherThread = new Thread("妈妈") { public void run() { try { Thread.sleep(1000); bank.login(Thread.currentThread()); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+" 登录超时被中断"); } } }; motherThread.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } motherThread.interrupt(); } }
结果:
妈妈线程一直等待爸爸线程释放锁,结果造成死锁。妈妈线程调用motherThread.interrupt(); 中断也不起做用。
6. 在登录获得锁时,调用lockInterruptibly(),允许中断等待的线程,使motherThread.interrupt()可用。
public class Bank { private static double money = 10000; private Lock lock = new ReentrantLock(); public void login(Thread currentUserThread) throws InterruptedException { lock.lockInterruptibly();//运行等待线程被中断 System.out.println(Thread.currentThread().getName() + " 登录进入银行" + " 当前银行余额 : " + money); } public void logout() { lock.unlock();//退出释放锁 System.out.println(Thread.currentThread().getName() + " 退出银行"); } public double withdraw(double withdrawMoney) { if (this.money < withdrawMoney) { System.out.println(Thread.currentThread().getName() + " 当前银行余额: " + this.money + " 余额不够"); return 0; } this.money -= withdrawMoney; System.out.println(Thread.currentThread().getName() + " 取款: " + withdrawMoney + " 当前银行余额: " + this.money); return withdrawMoney; } }
public class TestLock { public static void main(String[] args) { final Bank bank = new Bank(); //启动爸爸线程 Thread fatherThread = new Thread("爸爸") { public void run() { try { //爸爸登录 bank.login(Thread.currentThread()); //过2秒取10000 Thread.sleep(2000); bank.withdraw(10000); } catch (InterruptedException e) { e.printStackTrace(); } } }; fatherThread.start(); // 启动 妈妈 线程 Thread motherThread = new Thread("妈妈") { public void run() { try { Thread.sleep(1000); bank.login(Thread.currentThread()); } catch (InterruptedException e) { System.out.println(Thread.currentThread().getName()+" 登录超时被中断"); } } }; motherThread.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } motherThread.interrupt(); } }
结果:
爸爸 登录进入银行 当前银行余额 : 10000.0
妈妈 登录超时被中断
爸爸 取款: 10000.0 当前银行余额: 0.0