# 線程同步
[TOC]
## 多線程運行問題
在之前的學習中,我們發現在線程的運行過程中,線程什么時候運行是不確定的。
**多線程運行問題總結:**
* 各個線程是通過競爭cpu時間來獲取運行機會的;
* 各線程什么時候得到cpu時間占用多久,是不可預測的;
* 一個正在運行著的線程在什么時候被暫停是不確定的。
多線程的這些問題,在具體的開發中也會帶來不可預測的結果。
**場景:** 公司有一個公共的銀行賬號,在這個賬號中經常進行存取款操作。由于存取款操作可能同時進行,就有可能發生一定的風險。使用以下代碼模擬這樣一個過程
~~~java
public class Bank {
private String account;//賬戶
private int balance;//余額
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public int getBalance() {
return balance;
}
public void setBalance(int balance) {
this.balance = balance;
}
public Bank(String account, int balance) {
super();
this.account = account;
this.balance = balance;
}
public Bank() {
super();
}
@Override
public String toString() {
return "Bank [賬戶=" + account + ", 余額=" + balance + "]";
}
/**
* 存款方法
*/
public void saveAccount() {
//可以在不同的地方添加sleep()方法
//獲取當前賬戶余額
int balance = getBalance();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改存款
balance += 100;
//修改賬戶余額
setBalance(balance);
//輸出修改后的賬戶余額
System.out.println("輸出存款后的賬戶余額" + balance);
}
/**
* 取款方法
*/
public void drawAccount() {
//可以在不同的地方添加sleep()方法
//獲取當前的賬戶余額
int balance = getBalance();
//修改余額
balance = balance - 200;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改賬戶余額
setBalance(balance);
//輸出
System.out.println("取款后的賬戶余額" + balance);
}
}
~~~
~~~java
/**
* 存款
* @author LiXinRong
*
*/
public class SaveAccount implements Runnable {
Bank bank;
public SaveAccount(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
bank.saveAccount();
}
}
~~~
~~~java
/**
* 取款
* @author LiXinRong
*
*/
public class DrawAccount implements Runnable{
Bank bank;
public DrawAccount(Bank bank) {
this.bank = bank;
}
@Override
public void run() {
bank.drawAccount();
}
}
~~~
~~~java
public class SaveAndDrawDemo {
public static void main(String[] args) {
//創建賬戶,給定賬戶余額
Bank bank = new Bank("1001",1000);
//創建線程對象,并啟動線程
SaveAccount sa = new SaveAccount(bank);
DrawAccount da = new DrawAccount(bank);
Thread save = new Thread(sa);
Thread draw = new Thread(da);
save.start();
draw.start();
try {
//這里設置join方法,是為了先執行存取款線程,最后執行代碼末尾的打印銀行語句
save.join();
draw.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(bank);
}
}
~~~
當沒有對Bank類中的進行數據及時更新時,就有可能會造成運行的錯誤。尤其是我們今后在設計有關錢的代碼時,需要慎之又慎。
## 線程同步(線程互斥)處理
造成并發資源訪問不同步的主要原因在于沒有將若干個程序邏輯單元進行整體的鎖定,即當判斷數據和修改數據時,只允許一個線程進行處理,而其他線程需要等待當前線程執行完畢后才可以繼續執行,這樣就使得同一個時間段內,只允許一個線程執行操作,從而實現同步的處理。

>[success]就相當于,我們給CPU加了一把鎖
**使用synchronized關鍵字,可以實現同步處理。**
同步的關鍵是要為代碼加上“鎖”,對于鎖的操作程序有兩種:同步代碼塊,同步方法(成員方法、靜態方法);
**同步方法與同步代碼塊**
~~~java
/**
* 存款方法
*/
public synchronized void saveAccount() {
//可以在不同的地方添加sleep()方法
//獲取當前賬戶余額
int balance = getBalance();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改存款
balance += 100;
//修改賬戶余額
setBalance(balance);
//輸出修改后的賬戶余額
System.out.println("輸出存款后的賬戶余額" + balance);
}
/**
* 取款方法
*/
public void drawAccount() {
synchronized(this) {
//可以在不同的地方添加sleep()方法
//獲取當前的賬戶余額
int balance = getBalance();
//修改余額
balance = balance - 200;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//修改賬戶余額
setBalance(balance);
//輸出
System.out.println("取款后的賬戶余額" + balance);
}
}
~~~
當使用同步代碼塊的時候,當多個線程并發執行時,只允許一個線程執行此部分內容,從而實現同步處理操作。
同步代碼塊可以直接定義在某個方法中,使得方法的部分操作進行同步處理,但是如果某個方法中的全部操作都需要進行同步處理,則可以采用同步方法的形式進行定義。
>[warning]同步會造成處理性能下降
同步操作的本質在于同一個時間段內只允許有一個線程運行,所以在此線程對象未執行完的過程中其他線程對象將處于等待狀態,這樣就會造成程序處理性能下降。但是同步也帶來一些優點:數據的線程訪問安全。
## 線程死鎖
死鎖是在多線程開發中較為常見的一種不確定出現的問題,其所帶來的影響計時導致程序出現“假死”狀態。
同步保證了一個線程要等待另外一個線程執行完畢才會繼續執行,雖然在一個程序中,使用同步可以辦證資源共享操作的正確性,但是過多的同步也會產生問題。
**例如:**
>[info]現在張三想要李四的畫,李四想要張三的書,那么張三對李四說:“把你的畫給我,我就給你書”。李四也對張三說:“把你的書給我,我就給你畫”。此時,張三在等李四的回答,李四在等張三的回答。最終的結果,就是張三得不到李四的畫,李四得不到張三的書。
所謂死鎖,是指兩個線程都在等待對方先完成,造成程序的停滯狀態,一般程序的死鎖都是在程序運行時出現的。
在開發過程中回避線程的死鎖問題,是設計的難點。
- JAVA基礎
- JAVA開發準備
- JAVA介紹
- 開發JAVA的準備
- JAVA的運行原理
- JDK配置
- 我的第一個JAVA程序
- 類與對象
- 基礎語言要素
- 數據類型
- eclipse的安裝與使用
- 變量
- 直接量
- 運算符
- 流程控制
- 數組結構
- 面向對象
- 隱藏與封裝
- 深入構造器
- 類的繼承
- 多態
- 包裝類
- final修飾符
- 抽象類
- 接口
- 內部類
- 設計模式
- 單例模式
- 工廠模式
- 集合框架
- 集合排序
- 常用類學習
- 異常處理
- Java基礎測試
- 綜合案例一
- JAVA高級
- 泛型
- 多線程
- 線程的創建
- 線程的生命周期
- 線程同步
- 線程通信
- 輸入輸出流(I/O編程)
- File文件操作
- 字節流與字符流
- 數據庫
- 數據庫介紹
- 數據庫安裝
- SQL
- 表的基本操作
- 修改數據語句
- 數據檢索操作
- 多表數據操作
- 表結構設計
- 綜合應用