線程同步
線程安全問題(銀行取錢案例)
銀行取錢的基本流程基本上可以分爲以下步驟
- 用戶輸入賬戶,密碼,判斷用戶賬戶,密碼是否匹配(此處省略這一步)
- 用戶輸入取款金額
- 系統判斷賬戶餘額是否大於取款金額
- 如果餘額大於存款金額,則取款成功;如果餘額小於存款金額,則取款失敗
package thread;
/**
* @author Mike
* @use 賬戶類
*/
public class Account {
private String accountNo;
private double balance;
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public Account(){}
public Account(String accountNo,double balance){
this.accountNo=accountNo;
this.balance=balance;
}
public int hashCode(){
return accountNo.hashCode();
}
public boolean equals(Object object){
if(this == object){
return true;
}
if (object!=null && object.getClass() == Account.class){
Account target = (Account)object;
return target.getAccountNo().equals(accountNo);
}
return false;
}
}
package thread;
/**
* @author Mike
* @use 提供一個取錢的線程類,該線程類根據執行賬戶、取錢數量進行取錢操作
* ,取錢的邏輯是當餘額不足時,提示餘額不足,當餘額大於取的錢數時,餘額減少
*/
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(){
//synchronized (account){
if (account.getBalance() >= drawAmount){
System.out.println(getName()+"取錢成功,取出"+drawAmount);
account.setBalance(account.getBalance()-drawAmount);
System.out.println("\t餘額爲:"+account.getBalance());
}else {
System.out.println(getName() + "取錢失敗!餘額不足!");
}
//註釋內容爲同步代碼塊
// }
}
}
測試類:
package thread;
/**
* @author Mike
* @use 銀行取錢測試類
*/
public class DrawTest {
public static void main(String[] args) {
Account account = new Account("1234567" , 1000);
new DrawThread("甲" ,account ,800).start();
new DrawThread("乙",account,600).start();
}
}
加入兩個人同時從這個賬戶裏取錢,可能會出現餘額小於0的情況(可以這樣理解:甲取出錢,在餘額還沒有來得及更新的時候,乙也來取錢了),顯然這樣的情況我們不想讓它發生,所以我們用同步代碼塊來解決共享數據的安全性問題
同步代碼塊
synchronized(obj){
...
//此處的代碼就是同步代碼塊
}
對Account類進行修改
把18行註釋打開就好了,當然別忘了打開尾括號的註釋
同步鎖lock()
比較常用的是ReentrantLock(可重入鎖)(反正書上說這個常用,我還沒做過關於線程的項目,也不清楚)
使用時的代碼格式如下
class X{
//定義鎖對象
private final ReentrantLock lock = new ReentrantLock();
//定義需要保證線程安全的方法
public void m(){
//加鎖
lock.lock();
try{
//需要保證線程安全的代碼
}
//使用finally塊來保證釋放鎖
finally{
lock.unlock();
}
}
}
下面是銀行問題用lock改寫的代碼
Account類
package thread._16_6;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Mike
* @use 使用ReentrantLock重新完成鎖內容
*/
public class Account {
//定義鎖對象
private final ReentrantLock lock = new ReentrantLock();
private String accountNo;
private double balance;
public Account(){}
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;
}
public double getBalance() {
return this.balance;
}
//提供一個線程安全的draw()方法來完成取錢操作
public void draw(double drawAmount){
//加鎖
lock.lock();
try{
//賬戶餘額大於取錢數目
if (balance>=drawAmount){
System.out.println(Thread.currentThread().getName()+"取錢成功!吐出鈔票:"+drawAmount);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
balance -=drawAmount;
System.out.println("餘額爲:"+balance);
}else{
System.out.println(Thread.currentThread().getName()+"取錢失敗,餘額不足");
}
}finally {
//修改完成,釋放鎖
lock.unlock();
}
}
}
線程類
package thread._16_6;
/**
* @author Mike
* @use 提供一個取錢的線程類,該線程類根據執行賬戶、取錢數量進行取錢操作
* ,取錢的邏輯是當餘額不足時,提示餘額不足,當餘額大於取的錢數時,餘額減少
*/
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() {
account.draw(this.drawAmount);
}
}
測試類
package thread._16_6;
/**
* @author Mike
* @use 銀行取錢測試類
*/
public class DrawTest {
public static void main(String[] args) {
Account account = new Account("1234567" , 1000);
new DrawThread("甲" ,account ,800).start();
new DrawThread("乙",account,600).start();
}
}
其實到這裏我們又發現了一個問題
同步代碼塊相當於是廁所(惡臭的好例子),一個人進去,另外一個人只有等他出來才能進去。
假如有這樣一種情況,手紙是另一個同步代碼塊,甲拿了紙,想進廁所,乙在廁所,沒帶紙想用紙。哦吼,這就是個死鎖了。關於死鎖的問題下一次複習。