線程與線程之間執(zhí)行的任務(wù)不同,但線程與線程之間操作的數(shù)據(jù)相同他炊。
1掏熬、搭建示例
實現(xiàn)多線程同時讀取并輸出學(xué)生的信息
1.創(chuàng)建一個類用于存放學(xué)生的信息秒梅;
2.創(chuàng)建一個類用于存放輸入任務(wù);
3.創(chuàng)建一個類用于存放輸出任務(wù)疮丛;
4.使用傳參的方式讓兩個線程使用相同的參數(shù),實現(xiàn)線程間的通信履恩。
// 描述數(shù)據(jù)呢蔫,封裝到類中
class Student {
String name;
String sex;
}
// 描述輸入任務(wù)
class Input implements Runnable {
/*
* 使用傳參的方式
* 保證2個不同的任務(wù)
* 操作相同的數(shù)據(jù)
*/
private Student stu;
public Input(Student stu) {
this.stu = stu;
}
public void run() {
// 為了避免數(shù)據(jù)被覆蓋,使用if條件處理
int i = 1;
// 為了保證存儲盡可能多的數(shù)據(jù)片吊,使用while循環(huán)
while (true) {
if (i == 1) {
stu.name = "Tom";
stu.sex = "男";
} else {
stu.name = "Monica";
stu.sex = "女";
}
// 保證i的改變,使i在1與0之間來回切換
i = (i + 1) % 2;
}
}
}
// 描述輸出任務(wù)
class Output implements Runnable {
private Student stu;
public Output(Student stu) {
this.stu = stu;
}
public void run() {
// 使用while循環(huán)保證一直輸出
while (true) {
System.out.println(stu.sex + "生:" + stu.name);
}
}
}
public class Demo1 {
public static void main(String[] args) {
// 創(chuàng)建資源對象
Student stu = new Student();
// 創(chuàng)建輸入任務(wù)全谤,傳入?yún)?shù)stu
Input in = new Input(stu);
// 創(chuàng)建輸出任務(wù)爷贫,傳入?yún)?shù)stu
Output out = new Output(stu);
// 創(chuàng)建輸入線程
Thread t1 = new Thread(in);
// 創(chuàng)建輸出線程
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
此時結(jié)果如下:

之前存入的信息應(yīng)該是:"Tom"漫萄、"男","Monica"子刮、"女"窑睁,但結(jié)果卻出現(xiàn)了錯誤。出現(xiàn)這種錯誤的原因是:當輸入線程存儲完"Tom"橱赠、"男"之后箫津,向下運行,改變i的值為0饼拍,繼續(xù)循環(huán)田炭,當i=0時,剛儲存完"Monica"叨吮,此時CPU被輸出線程搶走,但輸入線程還未存儲"女"锋玲,因此輸出線程輸出了"Monica"涵叮、"男"。
2剿干、共享數(shù)據(jù)的安全問題
學(xué)生的信息在該示例代碼中就屬于共享數(shù)據(jù)穆刻,由輸入線程與輸出線程共同操作,如何實現(xiàn)數(shù)據(jù)安全榜轿,避免之前的錯誤朵锣,可以使用synchronized代碼塊包圍任務(wù)代碼,修改如下:
// 描述輸入任務(wù)
class Input implements Runnable {
private Student stu;
public Input(Student stu) {
this.stu = stu;
}
public void run() {
int i = 1;
while (true) {
//使用synchronized包圍輸入任務(wù)
synchronized (stu) {
if (i == 1) {
stu.name = "Tom";
stu.sex = "男";
} else {
stu.name = "Monica";
stu.sex = "女";
}
}
i = (i + 1) % 2;
}
}
}
// 描述輸出任務(wù)
class Output implements Runnable {
private Student stu;
public Output(Student stu) {
this.stu = stu;
}
public void run() {
while (true) {
//使用synchronized包圍輸出任務(wù)
synchronized (stu) {
System.out.println(stu.sex + "生:" + stu.name);
}
}
}
}
此時結(jié)果如下:

使用synchronized實現(xiàn)同步鎖的兩個條件:第一是要有兩個或以上的線程存在,第二是這多個線程使用同一把鎖诬烹。 這里首先有輸入和輸出兩個線程绞吁,其次,線程使用的鎖都是stu對象家破,實現(xiàn)了鎖的統(tǒng)一汰聋,保證了效果。
3烹困、設(shè)置線程等待與線程喚醒
根據(jù)修改后的結(jié)果,雖然錯誤消除了措近,但是會持續(xù)長時間輸出同一個學(xué)生信息瞭郑,出現(xiàn)這種情況的原因是輸出線程一直占據(jù)CPU進行輸出鸭你,這樣會造成性能的浪費,為了避免這種情況阁谆,實現(xiàn)當存入一個數(shù)據(jù)時愉老,再輸出一個數(shù)據(jù),可以使用等待喚醒機制焰盗,代碼如下:
// 描述數(shù)據(jù)咒林,封裝到類中
class Student {
String name;
String sex;
/*
* 創(chuàng)建判斷是否繼續(xù)輸入或輸出的標記
* 每當完成一次輸入或輸出修改flag的值
* 此時默認為false
*/
boolean flag;
}
// 描述輸入任務(wù)
class Input implements Runnable {
private Student stu;
public Input(Student stu) {
this.stu = stu;
}
public void run() {
int i = 1;
while (true) {
synchronized (stu) {
/*
* 需要先判斷是否能存入數(shù)據(jù)
* 如果flag為true
* 執(zhí)行等待
* 線程就停止在此處
* 不會執(zhí)行后續(xù)代碼
* 如果flag為false
* 則會跳過wait()方法
* 執(zhí)行存入數(shù)據(jù)
* 因此不需要添加else
*/
if(stu.flag){
try {
/*
* wait()方法要求處理異常
* 因為在run()方法內(nèi)部
* 只能使用try-catch塊包圍
* 不能拋出
* wait()方法還必須用在同步代碼中
* 使用鎖垫竞,也就是對象去調(diào)用該方法
* 也就是讓持有stu這把鎖的線程進入等待
* 之后等待的線程會放棄鎖
*/
stu.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (i == 1) {
stu.name = "Tom";
stu.sex = "男";
} else {
stu.name = "Monica";
stu.sex = "女";
}
// 改為適用于另一線程的flag的值
stu.flag = true;
/*
* 執(zhí)行喚醒另一線程
* 此時可以為空喚醒
* 同wait()方法一樣
* notify()也必須用在同步代碼中
* 需要用鎖去調(diào)用
* 也就是喚醒持有stu這把鎖的線程
*/
stu.notify();
}
i = (i + 1) % 2;
}
}
}
// 描述輸出任務(wù)
class Output implements Runnable {
private Student stu;
public Output(Student stu) {
this.stu = stu;
}
public void run() {
while (true) {
synchronized (stu) {
/*
* 需要先判斷是否能輸出
* 之前將flag的值改為true
* 因此如果判斷為false
* 執(zhí)行等待
* 如果判斷為ture
* 執(zhí)行輸出數(shù)據(jù)
*/
if(!stu.flag){
try {
stu.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(stu.sex + "生:" + stu.name);
// 改為適用于另一線程的flag的值
stu.flag = false;
// 執(zhí)行喚醒另一線程欢瞪,此時可以為空喚醒
stu.notify();
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
// 創(chuàng)建資源對象
Student stu = new Student();
// 創(chuàng)建輸入任務(wù)
Input in = new Input(stu);
// 創(chuàng)建輸出任務(wù)
Output out = new Output(stu);
// 創(chuàng)建輸入線程
Thread t1 = new Thread(in);
// 創(chuàng)建輸出線程
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
此時結(jié)果如下:

使用等待喚醒機制后遣鼓,實現(xiàn)了存儲一個數(shù)據(jù)后再輸出一個數(shù)據(jù)的效果,再簡單總結(jié)一下等待喚醒機制:
wait()方法宫补、notify()方法曾我、notifyAll()方法都必須用在同步代碼中,只有在同步代碼中才有鎖贫贝,而這3個方法都需要使用鎖來調(diào)用,哪個線程持有該鎖崇堵,哪個線程就會進入等待或喚醒狀態(tài)客燕。
- ** wait()方法:**讓線程進入等待狀態(tài),實際上是將線程放入了線程池赏廓;
- notify()方法:喚醒線程池中的任意一個線程傍妒,雖然是持有特定鎖的線程,但由于多個線程可以持有相同的鎖既忆,因此實際上是喚醒任意一個持有特定鎖的線程嗦玖;
- notifyAll()方法:喚醒所有線程。
之所以將這三個方法定義在Object類中庆亡,是因為這三個方法都需要鎖來調(diào)用捞稿,而任意一個對象都可以當做鎖,也就意味著任意一個對象都可以調(diào)用這三個方法彰亥,因此只能定義在所有類的父類衰齐,即Object類中。
4废酷、優(yōu)化完善示例代碼
class Student {
private String name;
private String sex;
private boolean flag;
// 創(chuàng)建存儲數(shù)據(jù)方法
public synchronized void set(String name, String sex) {
if (flag) {
try {
wait();// 因為此時鎖是this抹缕,可以省略
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.sex = sex;
flag = true;
notify();
}
// 創(chuàng)建輸出數(shù)據(jù)方法
public synchronized void out() {
if (!flag) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(sex + "生:" + name);
flag = false;
notify();
}
}
// 描述輸入任務(wù)
class Input implements Runnable {
private Student stu;
public Input(Student stu) {
this.stu = stu;
}
public void run() {
int i = 1;
while (true) {
if (i == 1) {
stu.set("Tom", "男");
} else {
stu.set("Monica", "女");
}
i = (i + 1) % 2;
}
}
}
// 描述輸出任務(wù)
class Output implements Runnable {
private Student stu;
public Output(Student stu) {
this.stu = stu;
}
public void run() {
while (true) {
stu.out();
}
}
}
public class Demo1 {
public static void main(String[] args) {
Student stu = new Student();
Input in = new Input(stu);
Output out = new Output(stu);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
注意:
隨著JKD版本的更新趴俘,在1.5版本之后出現(xiàn)比synchronized更加強大的實現(xiàn)同步鎖的方法,詳情參考使用Lock接口與Condition接口實現(xiàn)生產(chǎn)者與消費者寥闪。
版權(quán)聲明:歡迎轉(zhuǎn)載疲憋,歡迎擴散,但轉(zhuǎn)載時請標明作者以及原文出處柜某,謝謝合作喂击! ↓↓↓