同步锁Lock——通过显示定义同步锁对象来实现同步与synchronized相比更加灵活,可以具有差别很大的属性,并且支持多种condition对象。
在线程安全控制中,比较常用的是ReetrantLock(可重入锁)。使用Lock对象显示的加锁释放锁。
下面模拟银行账户取钱的场景来展示线程安全问题。
银行账户类
package com.lock;
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 double getBalance() {
return balance;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public void setBalance(double balance) {
this.balance = balance;
}
@Override
public int hashCode() {
return accountNo.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj != null && obj.getClass() == Account.class) {
Account target = (Account) obj;
return target.equals(obj);
}
return false;
}
}
为简便采用直接继承Thread方法创建线程
package com.lock;
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;
}
@Override
public void run() {
if (account.getBalance() >= drawAmount) {
System.out.println("吐出钞票:" + drawAmount);
account.setBalance(account.getBalance() - drawAmount);
System.out.println("余额为" + account.getBalance());
}else {
System.out.println(getName()+ "取钱失败");
}
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public double getDrawAmount() {
return drawAmount;
}
public void setDrawAmount(double drawAmount) {
this.drawAmount = drawAmount;
}
}
//测试
class DrawTest {
public static void main(String[] args) {
Account account = new Account("123456", 1000);
new DrawThread("甲", account, 800).start();
new DrawThread("乙", account, 800).start();
}
}
执行结果线程不安全
修改DrawThread类
package com.lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
private String accountNo;
private double balance;
//定义锁对象
private final ReentrantLock lock = new ReentrantLock();
public Account() {
}
public Account(String accountNo, double balance) {
this.accountNo = accountNo;
this.balance = balance;
}
public String getAccountNo() {
return accountNo;
}
public double getBalance() {
return balance;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public void setBalance(double balance) {
this.balance = balance;
}
//提供一个线程安全的draw方法Laura取钱
public void draw(double drawAmount) {
lock.lock();
//使用try finally 保证所一定会被释放
try {
if (balance >= drawAmount) {
System.out.println("吐出钞票:" + drawAmount);
balance = balance- drawAmount;
System.out.println("余额为" + balance);
}else {
System.out.println(Thread.currentThread().getName()+ "取钱失败");
}
}finally {
lock.unlock();
}
}
@Override
public int hashCode() {
return accountNo.hashCode();
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
} else if (obj != null && obj.getClass() == Account.class) {
Account target = (Account) obj;
return target.equals(obj);
}
return false;
}
}
上面的DrawThread类无需自己实现取钱操作,而是直接调用account的draw方法来执行取钱操作。保证了account对象的完整性和一致性,符合面向对象的思想。