多线程(四)线程的同步之同步方法

与同步代码块对应的,Java的多线程安全支持还提供了同步方法,同步方法就是使用synchronized关键字来修饰某个方法,该方法称为同步方法。对于同步方法而言,无须显式指定同步监视器,同步方法的同步监视器是this,也就是该对象本身。

通过使用同步方法可以非常方便地将某类编程线程安全的类,线程安全的类具有如下特征:

A、该类的对象可以被多个线程安全的访问;

B、每个线程调用该对象的任意方法之后都将得到正确的结果;

C、每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。

在学习Java基础的时候,我们接触过可变类和不可变类,其中不可变类总是线程安全的,因为它的对象的状态不可改变。但可变对象需要额外的方法来保证其线程安全。例如上面的Account就是一个可变类,它的account和balance两个属性都可变,当两个线程同时修改Account对象的balance属性时,程序就出现了异常。下面我们将Account类对balance的访问设置成线程安全的,那么程序只要把修改的balance的方法修改成同步方法即可。

package gblw.first;


public class Account {
	//封装账号编码、账号余额两个属性
	private String accountNo;
	private double balance;
	
	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属性的setter方法。
	public double getBalance() {
		return balance;
	}

	//提供一个线程安全draw方法来完成取钱操作
	public synchronized void draw(double drawAmount){
		//账户余额大于取钱数目
		if(balance>=drawAmount){
			//吐出钞票
			System.out.println(Thread.currentThread().getName()+"取钱成功!吐出钞票:"+drawAmount);
			try {
				Thread.sleep(1);
			} catch (Exception e) {
				e.printStackTrace();
			}
			//修改余额
			balance-=drawAmount;
			System.out.println("\t余额为:"+balance);
		}else{
			System.out.println(Thread.currentThread().getName()+"取钱失败!余额不足!");
		}
	}
	
	
	//下面两个方法根据accountNo来计算Account的hashCode和判断equals
	public int hashCode(){
		return accountNo.hashCode();
	}
	
	public boolean equals(Object obj){
		if(obj!=null&&obj.getClass()==Account.class){
			Account target=(Account) obj;
			return target.getAccountNo().equals(accountNo);
		}
		return false;
	}
}

package gblw.first;

import gblw.first.Account;

public class DrawThread extends Thread{
	//模拟用户账户
	private Account account;
	//当前取钱先吃所希望取的钱数
	private double drawAmount;
	
	public DrawThread(String name,Account account, double drawAmount) {
		super(name);
		this.account = account;
		this.drawAmount = drawAmount;
	}

	public void run() {
		//直接调用account对象的draw方法来执行取钱
		account.draw(drawAmount);
	}
	
	
}


上面DrawThread类无须自己实现取钱操作,而是直接调用account的draw方法来执行取钱。由于我们已经使用了synchronized关键字保证了draw方法的线程安全性,所以多线程并发调用draw方法也不会出现问题。

此时的程序把draw方法定义在Account里,而不是直接在run方法中实现取钱逻辑,这种做法更符合面向对象规则。在面向对象里有一种流行的设计方式:Domain Driven Design(即领域驱动设计,简称DDD),这种方式认为每个类都应该是完备的领域对象,例如Account它代表用户账户,它应该提供用户账户的相关方法,例如通过draw()方法来执行取钱操作(实际上还应该提供transfer等方法来完成转账等操作),而不是直接将setBalance()方法暴露出来任人操作,这样才可以更好地保证Account对象的完整性和一致性。

可变类的线程安全是以降低程序的运行效率作为代价的,为了减少线程安全所带来的负面影响,程序可以采用如下策略:

A、不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源(竞争资源也就是共享资源)的方法进行同步。例如上面的Account类中的accountNo属性就无须同步,所以程序只对draw方法进行同步控制。

B、如果可变类有两种运行环境:单线程环境和多线程环境,则应该为该可变类提供两种版本:线程不安全版本和线程安全版本。在单线程环境中使用线程不安全版本以保证性能,在多线程环境中使用线程安全版本。


释放同步监视器锁定?

任何



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值