多線程之間實現通訊
1 什麼是多線程之間的通訊
在併發編程中,我們需要處理兩個關鍵問題:線程之間如何通信及線程之間如何同步。通信是指線程之間以如何來交換信息。一般線程之間的通信機制有兩種:共享內存和消息傳遞。
Java的併發採用的是共享內存模型,Java線程之間的通信總是隱式進行,整個通信過程對程序員完全透明。如果編寫多線程程序的Java程序員不理解隱式進行的線程之間通信的工作機制,很可能會遇到各種奇怪的內存可見性問題。
2 通訊業務需求及實現
需求是:第一個線程寫入用戶,另一個線程取讀取用戶,實現讀一個,寫一個操作。
2.1 代碼實現基本實現
- 定義User類
package com.lijie;
public class User {
String name;
String sex;
}
- 輸入線程資源
package com.lijie;
//輸入類
class InputThread extends Thread {
private User user;
public InputThread(User user) {
this.user = user;
}
public void run() {
int count = 0;
while (true) {
if (count % 2 == 0) {
user.name = "張三";
user.sex = "男";
} else {
user.name = "小紅";
user.sex = "女";
}
count += 1;
}
}
}
- 輸出線程資源
package com.lijie;
//輸出類
class OutThread extends Thread {
private User user;
public OutThread(User user) {
this.user = user;
}
public void run() {
while (true) {
System.out.println(user.name + "====" + user.sex);
}
}
}
- 創建測試
package com.lijie;
public class Test {
public static void main(String[] args) {
User user = new User();
InputThread inputThread = new InputThread(user);
OutThread outThread = new OutThread(user);
inputThread.start();
outThread.start();
}
}
運行後你會發現,數據非常亂
2.2 使用synchronized解決線程安全問題
加上synchronized同步代碼塊
修改輸入和輸出線程資源倆個類:
package com.lijie;
//輸入類
class InputThread extends Thread {
private User user;
public InputThread(User user) {
this.user = user;
}
public void run() {
int count = 0;
synchronized (this) {
while (true) {
if (count % 2 == 0) {
user.name = "張三";
user.sex = "男";
} else {
user.name = "小紅";
user.sex = "女";
}
count += 1;
}
}
}
}
package com.lijie;
//輸出類
class OutThread extends Thread {
private User user;
public OutThread(User user) {
this.user = user;
}
public void run() {
synchronized (this) {
while (true) {
System.out.println(user.name + "====" + user.sex);
}
}
}
}
執行結果:發現數據沒有混亂了
2.3 改變需求
我覺得這樣輸出也很亂,我想要一個張三,一個小紅,在線程安全的情況下一個一個輸出如何實現:
2.4 wait、notify和方法
- 因爲涉及到對象鎖,他們必須都放在synchronized中來使用. wait、notify一定要在synchronized裏面進行使用。
- wait() :必須暫定當前正在執行的線程,並釋放資源鎖,讓其他線程可以有機會運行
- notify() :喚醒因鎖池中的線程,使之運行
2.6 修改代碼
- 修改User類,添加標識
package com.lijie;
public class User {
String name;
String sex;
//線程通訊標識
public boolean sign = false;
}
- 修改輸入類,添加wait和notify方法
package com.lijie;
//輸入類
class InputThread extends Thread {
private User user;
public InputThread(User user) {
this.user = user;
}
public void run() {
int count = 0;
while (true) {
synchronized (user) {
if (user.sign == true) {
try {
// 當前線程變爲等待,但是可以釋放鎖
user.wait();
} catch (Exception e) {
}
}
if (count % 2 == 0) {
user.name = "張三";
user.sex = "男";
} else {
user.name = "小紅";
user.sex = "女";
}
count += 1;
user.sign = true;
// 喚醒當前線程
user.notify();
}
}
}
}
- 修改輸出類,添加wait和notify方法
package com.lijie;
//輸出類
class OutThread extends Thread {
private User user;
public OutThread(User user) {
this.user = user;
}
public void run() {
while (true) {
synchronized (user) {
if (user.sign==false) {
try {
// 當前線程變爲等待,但是可以釋放鎖
user.wait();
} catch (Exception e) {
}
}
System.out.println(user.name + "====" + user.sex);
user.sign = false;
// 喚醒當前線程
user.notify();
}
}
}
}
執行結果:
2.7 wait與sleep區別
- sleep()方法,是屬於Thread類中的。
- wait()方法,則是屬於Object類中的。
- sleep()方法導致了程序暫停執行指定的時間,讓出cpu該其他線程,但是沒有釋放鎖
- wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備
3 Lock鎖
在 jdk1.5 之後,併發包中新增了 Lock 接口(以及相關實現類)用來實現鎖功能,Lock 接口提供了與 synchronized 關鍵字類似的同步功能,但需要在使用時手動獲取鎖和釋放鎖。
3.1 Lock寫法
private Lock lock = new ReentrantLock();
//使用完畢釋放後其他線程才能獲取鎖
public void lockTest(Thread thread) {
lock.lock();//獲取鎖
try {
} catch (Exception e) {
}finally {
lock.unlock(); //釋放鎖
}
3.2Lock鎖示例代碼
package com.lijie;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest {
private Lock lock = new ReentrantLock();
//使用完畢釋放後其他線程才能獲取鎖
public void lockTest(Thread thread) {
lock.lock();//獲取鎖
try {
System.out.println("線程" + thread.getName() + "獲取鎖");
} catch (Exception e) {
System.out.println("線程" + thread.getName() + "發生了異常");
} finally {
System.out.println("線程" + thread.getName() + "執行完畢釋放鎖");
lock.unlock(); //釋放鎖
}
}
public static void main(String[] args) {
LockTest lockTest = new LockTest();
Thread thread1 = new Thread(new Runnable() {
public void run() {
lockTest.lockTest(Thread.currentThread());
}
}, "線程一");
Thread thread2 = new Thread(new Runnable() {
public void run() {
lockTest.lockTest(Thread.currentThread());
}
}, "線程二");
// 啓動2個線程
thread2.start();
thread1.start();
}
}
4 Condition
Condition與Lock的關係就類似於synchronized與Object.wait()/signal()
- Conditionaw.ait():必須暫定當前正在執行的線程,並釋放資源鎖,讓其他線程可以有機會運行。和Object.wait()方法很相似。
- Conditionaw.singal():喚醒因鎖池中的線程,使之運行。和Object.wait()方法很相似。
4.1 Condition和Lock代碼示例
package com.lijie;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class User {
public String name;
public String sex;
//定義標識變量
public boolean flag = false;
//定義Lock鎖和ReentrantLock實現類
Lock lock = new ReentrantLock();
}
class InputThread extends Thread {
private User user;
//定義Condition屬性,靠他來釋放鎖和喚醒鎖
Condition newCondition;
//構造參數賦值
public InputThread(User user, Condition newCondition) {
this.user = user;
this.newCondition = newCondition;
}
//使用完畢釋放後其他線程才能獲取鎖
public void run() {
int count = 0;
while (true) {
try {
//獲取鎖
user.lock.lock();
if (user.flag) {
try {
//使當前線程等待,同時釋放當前鎖
newCondition.await();
} catch (Exception e) {
}
}
if (count == 0) {
user.name = "張三";
user.sex = "男";
} else {
user.name = "小紅";
user.sex = "女";
}
count = (count + 1) % 2;
user.flag = true;
//喚醒一個在等待中的線程
newCondition.signal();
} catch (Exception e) {
} finally {
//釋放鎖
user.lock.unlock();
}
}
}
}
class OutThread extends Thread {
private User user;
private Condition newCondition;
public OutThread(User user, Condition newCondition) {
this.user = user;
this.newCondition = newCondition;
}
public void run() {
while (true) {
try {
user.lock.lock();
if (!user.flag) {
try {
newCondition.await();
} catch (Exception e) {
}
}
System.out.println(user.name + "===" + user.sex);
user.flag = false;
newCondition.signal();
} catch (Exception e) {
} finally {
user.lock.unlock();
}
}
}
}
public class ThreadDemo1 {
public static void main(String[] args) {
User user = new User();
Condition newCondition = user.lock.newCondition();
InputThread inputThread = new InputThread(user, newCondition);
OutThread outThread = new OutThread(user, newCondition);
inputThread.start();
outThread.start();
}
}
5 synchronized與Lock的區別
- 首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;
- synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖;
- synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖;
- 用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,自動放棄的方法tryLock(),具有更完善的錯誤恢復機制。
- Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。