多线程学习(五)——线程同步之同步锁(Lock)(银行取钱问题)

14 篇文章 0 订阅
11 篇文章 0 订阅

        前面学习的synchronized是隐式的同步监视器。

        从java5开始,java提供了一种功能更加强大的线程同步机制——通过显式定义同步锁对象来实现同步,这种机制下,同步锁由Lock对象来充当。

        Lock提供了比synchronized方法和synchronized代码块更广泛的锁定操作,Lock允许实现更加灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象。Lock是控制多个线程对共享资源进行访问的工具,通常,锁提供了对共享资源的独占式访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该先获得lock对象。

        某些锁可能允许对共享资源并发访问,如ReadWriteLock(读写锁),Lock、ReadWriteLock是java5提供的两个根接口,并为Lock提供了ReentrantLock(可重入锁)实现类,为ReadWriteLock提供了ReentrantReadWriteLock实现类。

        在实现线程安全的控制中,比较常用的是ReentrantLock(可重入锁)。使用该Lock对象可以显式地加锁、释放锁,通常使用ReentrantLock的代码格式如下:

class Y
{
   //定义锁对象
   private final ReentrantLock lock = new ReentrantLock();
   //......
   //定义需要保证线程安全的方法
   public void m(){
      //加锁
      lock.lock();
      try{
         //需要保证线程安全的代码
         //....method body
      }
      //使用finnaly块来保证释放锁
      finally{
         lock.unlock();
      }
    }
}

使用ReentrantLock对象来进行同步,加锁和释放锁出现在不同的作用范围内时,通常建议使用finally块来确保在必要时释放锁。可以把以前的Account类改为下面的代码,也是线程安全的:

import java.util.concurrent.locks.ReentrantLock;

public class Account {
	//定义锁对象
	private final ReentrantLock lock = new ReentrantLock();	
	//封装账户编号、账户余额的两个成员变量
	private String accountNo;
	private double balance;
	public Account() {}
	//构造器
	public Account(String accountNo,double balance) {
		this.accountNo = accountNo;
		this.balance = balance;
	}
	public String getAccountNo() {
		return accountNo;
	}
	public void setAccountNo(String accountNo) {
		this.accountNo = accountNo;
	}
	//因为账户余额不允许随便修改,所以只为balance提供了getter方法
	public double getBalance() {
		return this.balance;
	}
	
	//提供一个线程安全的draw()方法来完成取钱操作
	public void draw(double drawMoney) {
		//加锁
		lock.lock();
		try {
			//账户余额大于取钱数目
			if(balance>=drawMoney) {
				//取出钞票
				System.out.println(Thread.currentThread().getName() +"取钱成功!吐出钞票:"+drawMoney);
				try {
					Thread.sleep(1);
				}catch(InterruptedException e) {
					e.printStackTrace();
				}
				//修改余额
				balance -= drawMoney ;
				System.out.println("\t余额为:"+ balance);
			}else {
				System.out.println(Thread.currentThread().getName()+"取钱失败!余额不足!");
			}
		}finally {
			//修改完后,释放锁
			lock.unlock();			
		}	
	}
	
	
	//下面两个方法根据accountNo来重写hashCode()和equals()方法
	public int hashCode() {
		return accountNo.hashCode();
	}
	public boolean equals(Object obj) {
		if(this == obj) 
			return true;
			if(obj!=null && obj.getClass() == Account.class) {
				Account target = (Account)obj;
				return target.getAccountNo().equals(accountNo);
			}		
		return false;
	}
}

其他两个类和我之前的博文“多线程学习(四)——线程同步之同步代码块和同步方法”一样,运行结果也是一样。

        ReentrantLock具有可重入性,即一个线程可以对已被加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持一个程序计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()加锁后,必须显式调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。

最后增加一个死锁的概念:

死锁:两个线程相互等待对方释放同步监视器时就会发生死锁,java虚拟机没有监测,也没有采取措施来处理死锁的情况,所以多线程编程时应该采取措施避免死锁的出现。一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程处于阻塞状态,无法继续。

(参考《疯狂Java讲义第3版》)

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值