与同步代码块对应的,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);
}
}
此时的程序把draw方法定义在Account里,而不是直接在run方法中实现取钱逻辑,这种做法更符合面向对象规则。在面向对象里有一种流行的设计方式:Domain Driven Design(即领域驱动设计,简称DDD),这种方式认为每个类都应该是完备的领域对象,例如Account它代表用户账户,它应该提供用户账户的相关方法,例如通过draw()方法来执行取钱操作(实际上还应该提供transfer等方法来完成转账等操作),而不是直接将setBalance()方法暴露出来任人操作,这样才可以更好地保证Account对象的完整性和一致性。
可变类的线程安全是以降低程序的运行效率作为代价的,为了减少线程安全所带来的负面影响,程序可以采用如下策略:
A、不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源(竞争资源也就是共享资源)的方法进行同步。例如上面的Account类中的accountNo属性就无须同步,所以程序只对draw方法进行同步控制。
B、如果可变类有两种运行环境:单线程环境和多线程环境,则应该为该可变类提供两种版本:线程不安全版本和线程安全版本。在单线程环境中使用线程不安全版本以保证性能,在多线程环境中使用线程安全版本。
释放同步监视器锁定?
任何