Java多线程学习总结--线程同步(2)

时间:2024-03-28 15:35:38

线程同步是为了让多个线程在共享数据时,保持数据的一致性。举个例子,有两个人同时取钱,假设用户账户余额是1000,第一个用户取钱800,在第一个用户取钱的同时,第二个用户取钱600。银行规定,用户不允许透支,当余额不足时,应该取钱失败。我们先来看一下,如果线程不同步,会出现什么情况。代码如下:

public class SynchronizeApp {

    /**
* @param args
*/
public static void main(String[] args) { // 获得账户
Account account = new Account();
account.setCardNo("95559");
account.setBalance(1000);
// 用户1取款800
DrawMoney user1 = new DrawMoney(account, 800);
// 用户1取款600
DrawMoney user2 = new DrawMoney(account, 600);
user1.start();
user2.start();
} } class DrawMoney extends Thread {
private Account account;
private double amount; public DrawMoney(Account account, double amount) {
this.account = account;
this.amount = amount;
} @Override
public void run() {
account.draw(amount);
}
} class Account {
private String cardNo;
private double balance; public String getCardNo() {
return cardNo;
} public void setCardNo(String cardNo) {
this.cardNo = cardNo;
} public double getBalance() {
return balance;
} public void setBalance(double balance) {
this.balance = balance;
} /**
* 用户取款
*
* @param amount
* ,取款数量
*/
public void draw(double amount) { if (amount > balance) {
System.out.println("金额不足!");
} else {
try {
// 模拟取款过程
Thread.sleep(100);
balance = balance - amount;
System.out.println("成功取款" + amount + "元, 最新余额为" + balance);
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
}

运行结果如下:

Java多线程学习总结--线程同步(2)

可见,如果没有线程同步,当两个线程同时取款时,就会出现数据错误。第二个线程取款时,读取到的账户余额是1000,所以可以执行取款操作,但是进行实际取款时,账户余额被第一个线程修改,实际余额是200,所以取出600后最新余额是-400,同样第一个用户也出现了数据错误,余额是1000,取款800后却变成了-400。这种情况是不允许的。

线程同步有两种方式,第一种是用synchronized关键字,第二种是用lock对象。

使用synchronized关键字可以对方法和代码块进行同步,使用synchronized关键字对方法进行同步时,将synchronized关键字放在方法返回类型前面,synchronized自动锁定当前对象。上边取款操使用synchronized关键字同步方法的代码如下:

class Account {
private String cardNo;
private double balance; public String getCardNo() {
return cardNo;
} public void setCardNo(String cardNo) {
this.cardNo = cardNo;
} public double getBalance() {
return balance;
} public void setBalance(double balance) {
this.balance = balance;
} /**
* 用户取款
*
* @param amount
* ,取款数量
*/
public synchronized void draw(double amount) { if (amount > balance) {
System.out.println("金额不足!");
} else {
try {
// 模拟取款过程
Thread.sleep(100);
balance = balance - amount;
System.out.println("成功取款" + amount + "元, 最新余额为" + balance);
} catch (InterruptedException e) {
e.printStackTrace();
}
} }
}

再次运行程序,得到如下结果:

Java多线程学习总结--线程同步(2)

使用synchronized同步代码块的代码如下:

/**
* 用户取款
*
* @param amount
* ,取款数量
*/
public void draw(double amount) { synchronized (this) {
if (amount > balance) {
System.out.println("金额不足!");
} else {
try {
// 模拟取款过程
Thread.sleep(100);
balance = balance - amount;
System.out.println("成功取款" + amount + "元, 最新余额为" + balance);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

还可以使用同步锁来对代码进行同步。使用同步锁时,先调用Lock对象的lock方法,代码执行完毕后,再调用Lock对象的unlock方法。在lock和unlock之间的代码是同步的,同一时间段内只能有一个线程能访问。为了保证能释放锁,把unlock方法放在finally语句块中是比较安全的,代码如下:

private final ReentrantLock lock = new ReentrantLock();

    /**
* 用户取款
*
* @param amount
* ,取款数量
*/
public void draw(double amount) {
lock.lock();
try {
if (amount > balance) {
System.out.println("金额不足!");
} else {
// 模拟取款过程
Thread.sleep(100);
balance = balance - amount;
System.out.println("成功取款" + amount + "元, 最新余额为" + balance);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}

运行代码,结果和使用synchronized同步方法的的运行结果一样。