​并发编程 之 锁 Lock 取款机例子

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
妈妈  登录超时被中断


Easy Learning Java


Easy Learning Design Patterns Java Practice


Easy Learning Data Structures & Algorithms Java Practice


Java Parsing Collection XML JSON


Easy Learning JDBC + MySQL


Easy Learning Javascript


Easy Learning Design Patterns Javascript


Easy Learning Design Patterns ES6+ Javascript


Easy Learning Data Structures & Algorithms Javascript


Easy Learning Data Structures & Algorithms ES6+Javascript


Easy Learning Oracle SQL


Easy Learning JDBC + Oracle


Easy Learning Python 3


Easy Learning Data Structures & Algorithms Python 3


Easy Learning Design Patterns Python 3


Easy Learning HTML CSS


Easy Learning MySQL SQL


Easy Learning C#


Easy Learning Data Structures & Algorithms C#


Easy Learning C


Easy Learning Data Structures & Algorithms C


Easy Learning Data Structures & Algorithms C++

​并发编程 之 锁 Lock 取款机例子