Concurrency Programming 二
- 并發之共享模型
- 應用之互斥
- 變數的執行緒安全
- 常見執行緒安全類
并發之共享模型
- 臨界區(Critical Section): 在一段代碼塊內, 存在多執行緒讀寫共享資源的操作, 稱此段代碼塊為臨界區
- 競爭條件(Race Condition), 競態條件: 不同的代碼, 多執行緒讀寫相同的共享資源時, 由于執行順序不同(位元組碼指令交錯), 而導致結果無法預測, 稱為發生了競態條件
應用之互斥
- 互斥(Mutex): 通過競爭獨占使用臨界區, 同一時刻, 保證只能有一個執行緒執行該代碼塊, 且訪問順序是無序的
- 同步(Sync): 也是同一時刻, 保證只能有一個執行緒執行該代碼塊, 但訪問順序是有序的
- 處理互斥的解決方案
- 阻塞式: synchronized, Lock
- 非阻塞式: 原子變數
- 通過 synchronized配合物件鎖實作互斥示例
物件鎖采用互斥的方式, 保證一個執行緒執行臨界區內的代碼時, 執行緒背景關系不被切換. 其它執行緒想獲得鎖就會被阻塞
class Room {
int value = 0;
public void increment() {
synchronized (this) {
value++;
}
}
public void decrement() {
synchronized (this) {
value--;
}
}
public int get() {
synchronized (this) {
return value;
}
}
}
public class App {
public static void main(String[] args) throws InterruptedException {
Room room = new Room();
Thread t1 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
room.increment();
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
room.decrement();
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(room.get()); // 0
}
}
*方法上加 synchronized意味著鎖住 this. 同一個類不同方法之間都加了 synchronized后, 則該物件的方法之間有互斥效應
class Test {
public synchronized void test() {}
}
等價于
class Test {
public void test() {
synchronized(this) {}
}
}
*static方法上加 synchronized意味著鎖住類
class Test {
public synchronized static void test() {}
}
等價于
class Test {
public static void test() {
synchronized(Test.class) {}
}
}
*注: 1. 類鎖和物件之間是沒有互斥的 2. 如果有個類內部都是類鎖方法, 同時 new了多個相關類物件, 此時不同物件之間也會有互斥效應
變數的執行緒安全
- 成員變數和靜態變數是否執行緒安全?
- 如果沒做共享, 則是執行緒安全的
- 如果做了共享, 則根據變數狀態是否能夠改變, 分兩種情況:
(-) 如果只有讀操作, 則是執行緒安全的
(-) 如果有讀寫操作, 則此代碼段屬臨界區, 需考慮執行緒安全
- 區域變數是否執行緒安全?
- 區域變數是執行緒安全的
- 但區域變數參考的物件, 則未必
(-) 如果該物件沒有逃離方法的作用訪問, 則是執行緒安全的
(-) 如果該物件逃離方法的作用范圍, 需考慮執行緒安全
- 成員變數: 共享 + 讀寫操作, 未加鎖
public static void main(String[] args) {
ThreadUnsafe test = new ThreadUnsafe();
for (int i = 0; i < 2; i++) {
new Thread(() -> {
test.method1(100);
}, "Thread" + i).start();
}
}
class ThreadUnsafe {
ArrayList<String> list = new ArrayList<>();
final public void method1(int loopNumber) {
//synchronized(this) { 此處為臨界區, 會產生競態條件
for (int i = 0; i < loopNumber; i++) {
//synchronized (this) { 此處為臨界區, 會產生競態條件; 由于 ArrayList不是執行緒安全的, 所以要保證原子性就需要加鎖后, 做讀寫操作
method2();
method3();
//}
}
//}
}
private void method2() {
list.add("1");
}
private void method3() {
list.remove(0);
}
}
# 情況是執行緒2還未 add, 執行緒1就 remove
Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:659)
at java.util.ArrayList.remove(ArrayList.java:498)
at org.example.base.threads.ThreadUnsafe.method3(App3.java:83)
at org.example.base.threads.ThreadUnsafe.method1(App3.java:72)
at org.example.base.threads.App3.lambda$main$0(App3.java:48)
at java.lang.Thread.run(Thread.java:748)
# 當成員變數 ArrayList, 改為區域變數, 則不會產生競態條件
class ThreadSafe {
public final void method1(int loopNumber) {
// 由于不同執行緒都會新生成 ArrayList變數, 因此不會產生競態條件
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < loopNumber; i++) {
method2(list);
method3(list);
}
}
private void method2(ArrayList<String> list) {
list.add("1");
}
public void method3(ArrayList<String> list) {
list.remove(0);
}
}
# 但是如果 method3未加 final關鍵字或是 public方法的話, 則可以建一個子類, 再覆寫 method3方法, 也可能會有執行緒安全問題
class ThreadSafeSubClass extends ThreadSafe {
@Override
public void method3(ArrayList<String> list) {
new Thread(() -> {
list.remove(0);
}).start();
}
}
常見執行緒安全類
-
String, Integer, StringBuffer, Random, Vector, Hashtable, java.util.concurrent包下的類等.
*注: 講這些是執行緒安全是指呼叫同一個實體的某個方法時, 是執行緒安全的. 而多個方法的組合不是原子的 -
多執行緒賣票簡單示例:
// 售票視窗
class TicketWindow {
private int count;
public TicketWindow(int count) {
this.count = count;
}
// 售票
public synchronized int sell(int num) {
if (this.count >= num) {
this.count -= num;
return num;
} else {
return 0;
}
}
public int getCount() {
return count;
}
}
public class App {
private static Random random = new Random();
// 隨機生成購買票數, 每次1~5個
private static int randomTicketAmount() {
return random.nextInt(5) + 1;
}
public static void main(String[] args) {
// 模擬多人買票
TicketWindow ticketWindow = new TicketWindow(2000);
// 保存所有的執行緒
List<Thread> list = new ArrayList<>();
// 累計賣出去的票數
List<Integer> ticketAmount = new Vector<>(); // 執行緒安全
for (int i = 0; i < 2000; i++) {
Thread t = new Thread(() -> {
int count = ticketWindow.sell(randomTicketAmount());
ticketAmount.add(count);
});
list.add(t);
t.start();
}
list.forEach((t) -> {
try {
t.join(); // 等待所有執行緒運行結束
} catch (InterruptedException e) {
e.printStackTrace();
}
});
// 賣出去的票總數
System.out.println("selled count: " + ticketAmount.stream().mapToInt(n -> n).sum());
// 剩余票數
System.out.println("remainder count: " + ticketWindow.getCount());
}
}
- 多執行緒轉賬簡單示例:
// 資金賬戶
class Account {
private int money;
public Account(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void setMoney(int money) {
this.money = money;
}
// 轉賬方法 1: 臨界區中, 發生了競態條件, 但是未加鎖
/*public void transfer(Account target, int amount) {
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}*/
// 轉賬方法 2: 無法互斥, 不同物件的 this, 如 Account target
/*public synchronized void transfer(Account target, int amount) {
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}*/
// 轉賬方法 3: 通過類鎖, 將不同物件互斥
public void transfer(Account target, int amount) {
synchronized(Account.class) {
if (this.money > amount) {
this.setMoney(this.getMoney() - amount);
target.setMoney(target.getMoney() + amount);
}
}
}
}
public class App {
private static Random random = new Random();
// 隨機生成金額, 每次1~100個
private static int randomMoney() {
return random.nextInt(100) + 1;
}
public static void main(String[] args) throws InterruptedException {
Account a = new Account(1000);
Account b = new Account(1000);
// 開辟2個執行緒
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
a.transfer(b, randomMoney());
}
}, "t1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
b.transfer(a, randomMoney());
}
}, "t2");
t1.start();
t2.start();
t1.join();
t2.join();
// 查看轉賬2000次后的總金額
System.out.println("total:" + (a.getMoney() + b.getMoney()));
}
}
如果您覺得有幫助,歡迎點贊哦 ~ 謝謝!!
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/267031.html
標籤:其他
