Lock 是个接口 ReentrantLock是他的实现类,下面通过 取款机案例 来剖析 他的 4 个常用方法。
1.爸爸 妈妈 同时在 ATM 上登录取款, 先不加任何锁,运行后看结果
package com.conccurrent.test.lock;
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();
}
}
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;
}
}
结果:
妈妈 登录进入银行 当前银行余额 : 10000.0
爸爸 登录进入银行 当前银行余额 : 10000.0
爸爸 取款 : 10000.0 当前银行余额 : 0.0
妈妈 当前银行余额 : 0.0 余额不够
妈妈登录显示银行余额10000,但是当她取钱时却显示 银行余额不足。 产生了数据不一致
2.正确的做法是 同一时刻 爸爸 或 妈妈 线程只能有一个登录银行取款 lock(), 另外一个线程需要等待, 直到 unlock() 释放锁
package com.conccurrent.test.lock2;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
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);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
};
motherThread.start();
}
}
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;
}
}
结果正确:
爸爸 登录进入银行 当前银行余额 : 10000.0
爸爸 取款 : 10000.0 当前银行余额 : 0.0
妈妈 登录进入银行 当前银行余额 : 0.0
爸爸 退出银行
妈妈 当前银行余额 : 0.0 余额不够
lock() 和 unlock() 成对出现,在 login(Thread currentUserThread) 登录方法中调用 lock() ,在 logout() 退出方法中调用了 unlock()
也就是说 Lock 类的锁机制允许在不同的方法中加锁 和 解锁, 而 synchronized 关键字只能在同一个方法中加锁 和 解锁。
3. 还可以通过 tryLock() 判断是否可以获得锁, 能获得锁 返回 true 否则返回 false
package com.conccurrent.test.lock3;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
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());// 爸爸登录
bank.withdraw(10000);
// 过 2秒取退出
Thread.sleep(2000);
bank.logout();
} 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) {
e.printStackTrace();
}
};
};
motherThread.start();
}
}
class Bank
{
private static double money=10000;
private Lock lock=new ReentrantLock();
public void login(Thread thread)
{
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;
}
}
结果:
爸爸 登录进入银行 当前银行余额 : 10000.0
爸爸 取款 : 10000.0 当前银行余额 : 0.0
妈妈 有人 已经登录进入银行 请稍等
爸爸 退出银行
4. 通过 tryLock(long time,TimeUnit timeUnit) 设置超过一段时间后重新进入
package com.conccurrent.test.lock4;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
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());// 爸爸登录
bank.withdraw(10000);
// 过 2秒取退出
Thread.sleep(2000);
bank.logout();
} 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) {
e.printStackTrace();
}
};
};
motherThread.start();
}
}
class Bank
{
private static double money=10000;
private Lock lock=new ReentrantLock();
public void login(Thread thread)
{
try {
if(!lock.tryLock(10, TimeUnit.SECONDS))//如果登录不成功 10 秒后在重新 尝试获得锁
{
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;
}
}
结果:妈妈 10 秒后重新尝试得到锁 登录进入了银行
爸爸 登录进入银行 当前银行余额 : 10000.0
爸爸 取款 : 10000.0 当前银行余额 : 0.0
爸爸 退出银行
妈妈 登录进入银行 当前银行余额 : 0.0
5.再看一下另外一种情况 如果爸爸线程调用 lock() 获得锁以后,没有调用 logout() unlock() 释放锁, 妈妈线程将会一直等待产生死锁。
即使调用 interrupt() 中断也没有作用。
package com.conccurrent.test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
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());// 爸爸登录
bank.withdraw(10000);
// 过 2秒取退出
Thread.sleep(2000);
} 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();
}
}
class Bank
{
private static double money=10000;
private Lock lock=new ReentrantLock();
public void login(Thread thread) throws InterruptedException
{
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;
}
}
结果 :
妈妈线程 一直等待 爸爸线程释放锁造成死锁, 妈妈线程调用 motherThread.interrupt(); 中断也不起做用。
6. 所以要让 motherThread.interrupt(); 中断起作用, 在登录获得锁的时候 要调用 lockInterruptibly() 允许中断等待的线程
package com.conccurrent.test;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
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());// 爸爸登录
bank.withdraw(10000);
// 过 2秒取退出
Thread.sleep(2000);
} 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();
}
}
class Bank
{
private static double money=10000;
private Lock lock=new ReentrantLock();
public void login(Thread thread) 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;
}
}
结果 :
爸爸 登录进入银行 当前银行余额 : 10000.0
爸爸 取款 : 10000.0 当前银行余额 : 0.0
妈妈 登录超时被中断