线程同步
账户:Account类
public class Account {
//账户编号、账户余额
private String accountNo;
private double balance;
public Account(){}
public Account(String accountNo,double balance)
{
this.accountNo=accountNo;
this.balance=balance;
}
public String getAccountNo()
{
return this.accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public int hashCode(){
return accountNo.hashCode();
}
//重写equals()和hashCode()方法
@Override
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;
}
}
取钱操作:DrawThread类
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()
{
if(account.getBalance()>=drawAmount)
{
System.out.println(getName()+"取钱成功"+drawAmount);
try {
Thread.sleep(1);//阻塞当前线程,使出现错误结果的现象更加明显
}catch (InterruptedException e)
{
e.printStackTrace();
}
//账户余额-取出的金额=当前账户余额
account.setBalance(account.getBalance()-drawAmount);
System.out.println(getName()+"取钱后的余额为:"+account.getBalance());
}
else {
System.out.println(getName()+"余额不足");
}
}
}
执行:
public class DrawTest {
public static void main(String[] args) {
Account account=new Account("12345",1000);
new DrawThread("甲",account,800).start();
new DrawThread("乙",account,800).start();
}
}
执行结果:
甲取钱成功800.0
乙取钱成功800.0
乙取钱后的余额为:200.0
甲取钱后的余额为:-600.0
结果分析:
甲和乙两个线程对同一个账户进行取钱操作,甲取钱后并未及时在账户上扣钱,乙在此时取钱,并在账户上扣钱,甲再次扣钱时,账户上只剩200,甲取钱后的余额为:-600.0。
这明显是错误的操作!
解决方法一:同步代码块
Java允许使用任何对象作为同步监视器,通常使用可能并发访问的共享资源充当同步监视器。
在run()方法中,加入同步代码块,将account账户作为同步监视器。
public void run() {
/*使用account作为同步监视器,任何线程进入下面同步代码块之前
必须先获得对account账户的锁定-其他线程无法获得锁,也就无法修改
这种做法符合:“加锁->修改->释放锁”
* */
synchronized (account) {
if (account.getBalance() >= drawAmount) {
System.out.println(getName() + "取钱成功" + drawAmount);
/* try {
Thread.sleep(1);
}catch (InterruptedException e)
{
e.printStackTrace();
}*/
account.setBalance(account.getBalance() - drawAmount);
System.out.println(getName() + "取钱后的余额为:" + account.getBalance());
} else {
System.out.println(getName() + "余额不足");
}
}
}
执行结果与预期相同:
甲取钱成功800.0
甲取钱后的余额为:200.0
乙余额不足
同步方法
使用同步方法可以实现线程安全的类,线程安全的类的特点:
1、该类的对象可以被多个线程安全地访问;
2、每个线程调用该对象的任意方法之后,都能得到正确的结果;
3、每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。
Account类:
public class Account {
//账户编号、账户余额
private String accountNo;
private double balance;
public Account(){}
public Account(String accountNo,double balance)
{
this.accountNo=accountNo;
this.balance=balance;
}
public String getAccountNo()
{
return this.accountNo;
}
//账户余额不允许随便修改,只提供get方法
public double getBalance() {
return balance;
}
public int hashCode(){
return accountNo.hashCode();
}
@Override
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;
}
//添加取钱的同步方法,同一时刻只允许一个线程调用
public synchronized void draw(double drawAmount)
{
if (balance >= drawAmount) {
System.out.println(Thread.currentThread().getName() + "取钱成功" + drawAmount);
balance-=drawAmount;
System.out.println(Thread.currentThread().getName() + "取钱后的余额为:" + balance);
} else {
System.out.println(Thread.currentThread().getName() + "余额不足");
}
}
}
DrawThread类:
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(drawAmount);
}
}
执行结果符合预期
释放同步监视器的锁定条件
同步锁
改写Account类:
//定义对象锁
private final ReentrantLock lock=new ReentrantLock();
public void draw(double drawAmount)
{
lock.lock();
if (balance >= drawAmount) {
System.out.println(Thread.currentThread().getName() + "取钱成功" + drawAmount);
/* try {
Thread.sleep(1);
}catch (InterruptedException e)
{
e.printStackTrace();
}*/
balance-=drawAmount;
System.out.println(Thread.currentThread().getName() + "取钱后的余额为:" + balance);
} else {
System.out.println(Thread.currentThread().getName() + "余额不足");
}
lock.unlock();
}
可重入性:一个线程可以对已被加锁的ReentrantLock锁再次加锁。