<pre>
理解程序、進(jìn)程、線程的概念
程序可以理解為靜態(tài)的代碼
進(jìn)程可以理解為執(zhí)行中的程序
線程可以理解為進(jìn)程的進(jìn)一步細(xì)分,程序的一條執(zhí)行路徑
使用多線程的優(yōu)點(diǎn):
提高應(yīng)用程序的響應(yīng)。對(duì)圖形化界面更有意義,可增強(qiáng)用戶體驗(yàn)。
提高計(jì)算機(jī)系統(tǒng)CPU的利用率
改善程序結(jié)構(gòu)。將既長(zhǎng)又復(fù)雜的進(jìn)程分為多個(gè)線程,獨(dú)立運(yùn)行,利于理解和修改
在java中要想實(shí)現(xiàn)多線程,有兩種手段,一種是繼續(xù)Thread類,另外一種是實(shí)現(xiàn)Runable接口
繼承java.lang.Thread類
下面來(lái)看一個(gè)簡(jiǎn)單的實(shí)例:
class Thread1 extends Thread{
private String name;
public Thread1(String name) {
this.name=name;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "運(yùn)行 : " + i);
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=new Thread1("B");
mTh1.start();
mTh2.start();
}
}
輸出
輸出:
A運(yùn)行 : 0
B運(yùn)行 : 0
A運(yùn)行 : 1
A運(yùn)行 : 2
A運(yùn)行 : 3
A運(yùn)行 : 4
B運(yùn)行 : 1
B運(yùn)行 : 2
B運(yùn)行 : 3
B運(yùn)行 : 4
再運(yùn)行一下:
A運(yùn)行 : 0
B運(yùn)行 : 0
B運(yùn)行 : 1
B運(yùn)行 : 2
B運(yùn)行 : 3
B運(yùn)行 : 4
A運(yùn)行 : 1
A運(yùn)行 : 2
A運(yùn)行 : 3
A運(yùn)行 : 4
說(shuō)明:
程序啟動(dòng)運(yùn)行main時(shí)候,java虛擬機(jī)啟動(dòng)一個(gè)進(jìn)程,主線程main在main()調(diào)用時(shí)候被創(chuàng)建。隨著調(diào)用Thread1的兩個(gè)對(duì)象的start方法,另外兩個(gè)線程也啟動(dòng)了,這樣,整個(gè)應(yīng)用就在多線程下運(yùn)行。
以下是關(guān)系到線程運(yùn)行狀態(tài)的幾個(gè)方法:
1)start方法
start()用來(lái)啟動(dòng)一個(gè)線程,當(dāng)調(diào)用start方法后,系統(tǒng)才會(huì)開啟一個(gè)新的線程來(lái)執(zhí)行用戶定義的子任務(wù),在這個(gè)過程中,會(huì)為相應(yīng)的線程分配需要的資源。
2)run方法
run()方法是不需要用戶來(lái)調(diào)用的,當(dāng)通過start方法啟動(dòng)一個(gè)線程之后,當(dāng)線程獲得了CPU執(zhí)行時(shí)間,便進(jìn)入run方法體去執(zhí)行具體的任務(wù)。注意,繼承Thread類必須重寫run方法,在run方法中定義具體要執(zhí)行的任務(wù)。
3)sleep方法
sleep相當(dāng)于讓線程睡眠,交出CPU,讓CPU去執(zhí)行其他的任務(wù)。
實(shí)現(xiàn)java.lang.Runnable接口
用Runnable也是非常常見的一種,我們只需要重寫run方法即可。下面也來(lái)看個(gè)實(shí)例:
class Thread2 implements Runnable{
private String name;
public Thread2(String name) {
this.name=name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + "運(yùn)行 : " + i);
try {
Thread.sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
new Thread(new Thread2("C")).start();
new Thread(new Thread2("D")).start();
}
}
輸出
輸出:
C運(yùn)行 : 0
D運(yùn)行 : 0
D運(yùn)行 : 1
C運(yùn)行 : 1
D運(yùn)行 : 2
C運(yùn)行 : 2
D運(yùn)行 : 3
C運(yùn)行 : 3
D運(yùn)行 : 4
C運(yùn)行 : 4
說(shuō)明:
Thread2類通過實(shí)現(xiàn)Runnable接口,使得該類有了多線程類的特征。run()方法是多線程程序的一個(gè)約定。所有的多線程代碼都在run方法里面。Thread類實(shí)際上也是實(shí)現(xiàn)了Runnable接口的類。
在啟動(dòng)的多線程的時(shí)候,需要先通過Thread類的構(gòu)造方法Thread(Runnable target) 構(gòu)造出對(duì)象,然后調(diào)用Thread對(duì)象的start()方法來(lái)運(yùn)行多線程代碼。
實(shí)際上所有的多線程代碼都是通過運(yùn)行Thread的start()方法來(lái)運(yùn)行的。因此,不管是擴(kuò)展Thread類還是實(shí)現(xiàn)Runnable接口來(lái)實(shí)現(xiàn)多線程,最終還是通過Thread的對(duì)象的API來(lái)控制線程的,熟悉Thread類的API是進(jìn)行多線程編程的基礎(chǔ)。
Thread和Runnable的區(qū)別
對(duì)比一下繼承的方式 vs 實(shí)現(xiàn)的方式
1.聯(lián)系:public class Thread implements Runnable(繼承的方式的Thread也實(shí)現(xiàn)了Runnable接口)
2.哪個(gè)方式好?
實(shí)現(xiàn)的方式優(yōu)于繼承的方式 why?
① 避免了java單繼承的局限性
② 如果多個(gè)線程要操作同一份資源(或數(shù)據(jù)),更適合使用實(shí)現(xiàn)的方式
看一個(gè)例子:
//模擬火車站售票窗口,開啟三個(gè)窗口售票,總票數(shù)為100張
//存在線程的安全問題
class Window extends Thread {
int ticket = 100;
public void run() {
while (true) {
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + "售票,票號(hào)為:"+ ticket--);
} else {
break;
}
}
}
}
public class TestWindow {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("窗口1");
w2.setName("窗口2");
w3.setName("窗口3");
w1.start();
w2.start();
w3.start();
}
}
class Window implements Runnable {
int ticket = 100;//要將全局變量聲明為靜態(tài),不然每個(gè)對(duì)象都有這個(gè)屬性,會(huì)賣出300張票
public void run() {
while (true) {
if (ticket > 0) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票號(hào)為:"+ ticket--);
} else {
break;
}
}
}
}
public class Main {
//模擬火車站售票窗口,開啟三個(gè)窗口售票,總票數(shù)為100張
//存在線程的安全問題
public static void main(String[] args) {
Window w1 = new Window();
Thread t1 = new Thread(w1, "t1");
Thread t2 = new Thread(w1, "t2");
Thread t3 = new Thread(w1, "t3");
t1.start();
t2.start();
t3.start();
}
}
線程的生命周期
線程調(diào)度
1、調(diào)整線程優(yōu)先級(jí):Java線程有優(yōu)先級(jí),優(yōu)先級(jí)高的線程會(huì)獲得較多的運(yùn)行機(jī)會(huì)。
Java線程的優(yōu)先級(jí)用整數(shù)表示,取值范圍是1~10,Thread類有以下三個(gè)靜態(tài)常量:
static int MAX_PRIORITY
線程可以具有的最高優(yōu)先級(jí),取值為10。
static int MIN_PRIORITY
線程可以具有的最低優(yōu)先級(jí),取值為1。
static int NORM_PRIORITY
分配給線程的默認(rèn)優(yōu)先級(jí),取值為5。
Thread類的setPriority()和getPriority()方法分別用來(lái)設(shè)置和獲取線程的優(yōu)先級(jí)。
2、線程睡眠:Thread.sleep(long millis)方法,使線程轉(zhuǎn)到阻塞狀態(tài)。millis參數(shù)設(shè)定睡眠的時(shí)間,以毫秒為單位。當(dāng)睡眠結(jié)束后,就轉(zhuǎn)為就緒(Runnable)狀態(tài)。sleep()平臺(tái)移植性好。
3、線程等待:Object類中的wait()方法,導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對(duì)象的 notify() 方法或 notifyAll() 喚醒方法。
4、線程讓步:Thread.yield() 方法,暫停當(dāng)前正在執(zhí)行的線程對(duì)象,把執(zhí)行機(jī)會(huì)讓給相同或者更高優(yōu)先級(jí)的線程。
5、線程加入:join()方法,等待其他線程終止。在當(dāng)前線程中調(diào)用另一個(gè)線程的join()方法,則當(dāng)前線程轉(zhuǎn)入阻塞狀態(tài),直到另一個(gè)進(jìn)程運(yùn)行結(jié)束,當(dāng)前線程再由阻塞轉(zhuǎn)為就緒狀態(tài)。
6、線程喚醒:Object類中的notify()方法,喚醒在此對(duì)象監(jiān)視器上等待的單個(gè)線程。
注意:Thread中suspend()和resume()兩個(gè)方法在JDK1.5中已經(jīng)廢除,不再介紹。因?yàn)橛兴梨i傾向。
代碼示例
join():指等待某線程終止。
為什么要用join()方法
在很多情況下,主線程生成并起動(dòng)了子線程,如果子線程里要進(jìn)行大量的耗時(shí)的運(yùn)算,主線程往往將于子線程之前結(jié)束,但是如果主線程處理完其他的事務(wù)后,需要用到子線程的處理結(jié)果,也就是主線程需要等待子線程執(zhí)行完成之后再結(jié)束,這個(gè)時(shí)候就要用到j(luò)oin()方法了。
class Thread1 extends Thread{
private String name;
public Thread1(String name) {
super(name);
this.name=name;
}
public void run() {
System.out.println(Thread.currentThread().getName() + " 線程運(yùn)行開始!");
for (int i = 0; i < 5; i++) {
System.out.println("子線程"+name + "運(yùn)行 : " + i);
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 線程運(yùn)行結(jié)束!");
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主線程運(yùn)行開始!");
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=new Thread1("B");
mTh1.start();
mTh2.start();
System.out.println(Thread.currentThread().getName()+ "主線程運(yùn)行結(jié)束!");
}
}
輸出結(jié)果:
main主線程運(yùn)行開始!
main主線程運(yùn)行結(jié)束!
B 線程運(yùn)行開始!
子線程B運(yùn)行 : 0
A 線程運(yùn)行開始!
子線程A運(yùn)行 : 0
子線程B運(yùn)行 : 1
子線程A運(yùn)行 : 1
子線程A運(yùn)行 : 2
子線程A運(yùn)行 : 3
子線程A運(yùn)行 : 4
A 線程運(yùn)行結(jié)束!
子線程B運(yùn)行 : 2
子線程B運(yùn)行 : 3
子線程B運(yùn)行 : 4
B 線程運(yùn)行結(jié)束!
發(fā)現(xiàn)主線程比子線程早結(jié)束
加join
public class Main {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主線程運(yùn)行開始!");
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=new Thread1("B");
mTh1.start();
mTh2.start();
try {
mTh1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
mTh2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "主線程運(yùn)行結(jié)束!");
}
}
運(yùn)行結(jié)果:
main主線程運(yùn)行開始!
A 線程運(yùn)行開始!
子線程A運(yùn)行 : 0
B 線程運(yùn)行開始!
子線程B運(yùn)行 : 0
子線程A運(yùn)行 : 1
子線程B運(yùn)行 : 1
子線程A運(yùn)行 : 2
子線程B運(yùn)行 : 2
子線程A運(yùn)行 : 3
子線程B運(yùn)行 : 3
子線程A運(yùn)行 : 4
子線程B運(yùn)行 : 4
A 線程運(yùn)行結(jié)束!
主線程一定會(huì)等子線程都結(jié)束了才結(jié)束
yield
Thread.yield()方法作用是:暫停當(dāng)前正在執(zhí)行的線程對(duì)象,并執(zhí)行其他線程。
yield()應(yīng)該做的是讓當(dāng)前運(yùn)行線程回到可運(yùn)行狀態(tài),以允許具有相同優(yōu)先級(jí)的其他線程獲得運(yùn)行機(jī)會(huì)。因此,使用yield()的目的是讓相同優(yōu)先級(jí)的線程之間能適當(dāng)?shù)妮嗈D(zhuǎn)執(zhí)行。但是,實(shí)際中無(wú)法保證yield()達(dá)到讓步目的,因?yàn)樽尣降木€程還有可能被線程調(diào)度程序再次選中。
結(jié)論:yield()從未導(dǎo)致線程轉(zhuǎn)到等待/睡眠/阻塞狀態(tài)。在大多數(shù)情況下,yield()將導(dǎo)致線程從運(yùn)行狀態(tài)轉(zhuǎn)到可運(yùn)行狀態(tài),但有可能沒有效果。
class ThreadYield extends Thread{
public ThreadYield(String name) {
super(name);
}
@Override
public void run() {
for (int i = 1; i <= 50; i++) {
System.out.println("" + this.getName() + "-----" + i);
// 當(dāng)i為30時(shí),該線程就會(huì)把CPU時(shí)間讓掉,讓其他或者自己的線程執(zhí)行(也就是誰(shuí)先搶到誰(shuí)執(zhí)行)
if (i ==30) {
this.yield();
}
}
}
}
public class Main {
public static void main(String[] args) {
ThreadYield yt1 = new ThreadYield("張三");
ThreadYield yt2 = new ThreadYield("李四");
yt1.start();
yt2.start();
}
}
運(yùn)行結(jié)果:
第一種情況:李四(線程)當(dāng)執(zhí)行到30時(shí)會(huì)CPU時(shí)間讓掉,這時(shí)張三(線程)搶到CPU時(shí)間并執(zhí)行。
第二種情況:李四(線程)當(dāng)執(zhí)行到30時(shí)會(huì)CPU時(shí)間讓掉,這時(shí)李四(線程)搶到CPU時(shí)間并執(zhí)行。
sleep()和yield()的區(qū)別
sleep()和yield()的區(qū)別):sleep()使當(dāng)前線程進(jìn)入停滯狀態(tài),所以執(zhí)行sleep()的線程在指定的時(shí)間內(nèi)肯定不會(huì)被執(zhí)行;yield()只是使當(dāng)前線程重新回到可執(zhí)行狀態(tài),所以執(zhí)行yield()的線程有可能在進(jìn)入到可執(zhí)行狀態(tài)后馬上又被執(zhí)行。
sleep 方法使當(dāng)前運(yùn)行中的線程睡眼一段時(shí)間,進(jìn)入不可運(yùn)行狀態(tài),這段時(shí)間的長(zhǎng)短是由程序設(shè)定的,yield 方法使當(dāng)前線程讓出 CPU 占有權(quán),但讓出的時(shí)間是不可設(shè)定的。實(shí)際上,yield()方法對(duì)應(yīng)了如下操作:先檢測(cè)當(dāng)前是否有相同優(yōu)先級(jí)的線程處于同可運(yùn)行狀態(tài),如有,則把 CPU 的占有權(quán)交給此線程,否則,繼續(xù)運(yùn)行原來(lái)的線程。所以yield()方法稱為“退讓”,它把運(yùn)行機(jī)會(huì)讓給了同等優(yōu)先級(jí)的其他線程。
線程的同步
1、線程安全問題存在的原因:
由于一個(gè)線程在操作共享數(shù)據(jù)過程中,未執(zhí)行完畢的情況下,另外的線程參與進(jìn)來(lái),導(dǎo)致共享數(shù)據(jù)存在了安全問題。
2、如何解決線程安全問題
必須讓一個(gè)線程操作共享數(shù)據(jù)完畢以后,其它線程才有機(jī)會(huì)參與共享數(shù)據(jù)的操作。
3、java如何實(shí)現(xiàn)線程安全:線程的同步機(jī)制
方式一:同步代碼塊
synchronized(同步監(jiān)視器){
//需要被同步的代碼塊(即為操作共享數(shù)據(jù)的代碼)
}
1、共享數(shù)據(jù):多個(gè)線程共同操作的同一個(gè)數(shù)據(jù)(變量)
2、同步監(jiān)視器:由任何一個(gè)類的對(duì)象來(lái)充當(dāng)。哪個(gè)線程獲取此監(jiān)視器,誰(shuí)就執(zhí)行大括號(hào)里被同步的代碼。俗稱:鎖
注:在實(shí)現(xiàn)Runnable接口的方式中,考慮同步的話,可以使用this來(lái)充當(dāng)鎖。但是在繼承的方式中,慎用this
class Window2 implements Runnable {
int ticket = 1000;// 共享數(shù)據(jù)
public void run() {
while (true) {
synchronized (this) {//this表示當(dāng)前對(duì)象,本題中即為w
if (ticket > 0) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+ "售票,票號(hào)為:" + ticket--);
}
}
}
}
}
public class TestWindow2 {
public static void main(String[] args) {
Window2 w = new Window2();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
方式二:同步方法
將操作共享數(shù)據(jù)的方法聲明為synchronized. 即此方法為同步方法,能夠保證當(dāng)其中一個(gè)線程執(zhí)行此方法時(shí),其他線程在外等待直至此線程執(zhí)行完此方法。
同步方法的鎖:this(不用顯式的寫)
class Window4 implements Runnable {
int ticket = 1000;// 共享數(shù)據(jù)
public void run() {
while (true) {
show();
}
}
public synchronized void show() {
if (ticket > 0) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售票,票號(hào)為:"
+ ticket--);
}
}
}
public class TestWindow4 {
public static void main(String[] args) {
Window4 w = new Window4();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
練習(xí)
銀行有一個(gè)賬戶。有兩個(gè)儲(chǔ)戶分別向同一個(gè)賬戶存3000元,每次存1000,存3次。每次存完打印賬戶余額。
class Account{
double balance;//余額
public Account(){
}
//存錢
public synchronized void deposit(double amt){
notify();
balance+=amt;
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":"+balance);
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Customer extends Thread{
Account account;
public Customer(Account account){
this.account=account;
}
public void run(){
for(int i=0;i<3;i++){
account.deposit(1000);
}
}
}
public class TestAccount {
public static void main(String[] args) {
Account acct = new Account();
Customer c1 = new Customer(acct);
Customer c2 = new Customer(acct);
c1.setName("甲");
c2.setName("乙");
c1.start();
c2.start();
}
}
小結(jié):
釋放鎖的操作
當(dāng)前線程的同步方法、同步代碼塊執(zhí)行結(jié)束
當(dāng)前線程在同步代碼塊、同步方法中遇到break、return終止了該代碼塊、該方法的繼續(xù)執(zhí)行。
當(dāng)前線程在同步代碼塊、同步方法中出現(xiàn)了未處理的Error或Exception,導(dǎo)致異常結(jié)束
當(dāng)前線程在同步代碼塊、同步方法中執(zhí)行了線程對(duì)象的wait()方法,當(dāng)前線程暫停,并釋放鎖。
不會(huì)釋放鎖的操作
線程執(zhí)行同步代碼塊或同步方法時(shí),程序調(diào)用Thread.sleep()、Thread.yield()方法暫停當(dāng)前線程的執(zhí)行
線程的死鎖問題
死鎖:不同的線程分別占用對(duì)方需要的同步資源不放棄,都在等待對(duì)方放棄自己需要的同步資源,就形成了線程的死鎖
解決方法:專門的算法、原則;盡量減少同步資源的定義
/死鎖的問題:處理線程同步時(shí)容易出現(xiàn)。
//不同的線程分別占用對(duì)方需要的同步資源不放棄,都在等待對(duì)方放棄自己需要的同步資源,就形成了線程的死鎖
//寫代碼時(shí),要避免死鎖!
public class TestDeadLock {
static StringBuffer sb1 = new StringBuffer();
static StringBuffer sb2 = new StringBuffer();
public static void main(String[] args) {
new Thread() {
public void run() {
synchronized (sb1) {
try {
Thread.currentThread().sleep(10);//問題放大
} catch (InterruptedException e) {
e.printStackTrace();
}
sb1.append("A");
synchronized (sb2) {
sb2.append("B");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
}.start();
new Thread() {
public void run() {
synchronized (sb2) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
sb1.append("C");
synchronized (sb1) {
sb2.append("D");
System.out.println(sb1);
System.out.println(sb2);
}
}
}
}.start();
}
}
線程通信
wait() 與 notify() 和 notifyAll()
wait():令當(dāng)前線程掛起并放棄CPU、同步資源,使別的線程可訪問并修改共享資源,而當(dāng)前線程排隊(duì)等候再次對(duì)資源的訪問
notify():?jiǎn)拘颜谂抨?duì)等待同步資源的線程中優(yōu)先級(jí)最高者結(jié)束等待
notifyAll ():?jiǎn)拘颜谂抨?duì)等待資源的所有線程結(jié)束等待.
wait是指在一個(gè)已經(jīng)進(jìn)入了同步鎖的線程內(nèi),讓自己暫時(shí)讓出同步鎖,以便其他正在等待此鎖的線程可以得到同步鎖并運(yùn)行,只有其他線程調(diào)用了notify方法(notify并不釋放鎖,只是告訴調(diào)用過wait方法的線程可以去參與獲得鎖的競(jìng)爭(zhēng)了,但不是馬上得到鎖,因?yàn)殒i還在別人手里,別人還沒釋放),調(diào)用wait方法的一個(gè)或多個(gè)線程就會(huì)解除wait狀態(tài),重新參與競(jìng)爭(zhēng)對(duì)象鎖,程序如果可以再次得到鎖,就可以繼續(xù)向下運(yùn)行。
1)wait()、notify()和notifyAll()方法是本地方法,并且為final方法,無(wú)法被重寫。
2)當(dāng)前線程必須擁有此對(duì)象的monitor(即鎖),才能調(diào)用某個(gè)對(duì)象的wait()方法能讓當(dāng)前線程阻塞,
(這種阻塞是通過提前釋放synchronized鎖,重新去請(qǐng)求鎖導(dǎo)致的阻塞,這種請(qǐng)求必須有其他線程通過notify()或者notifyAll()喚醒重新競(jìng)爭(zhēng)獲得鎖)
3)調(diào)用某個(gè)對(duì)象的notify()方法能夠喚醒一個(gè)正在等待這個(gè)對(duì)象的monitor的線程,如果有多個(gè)線程都在等待這個(gè)對(duì)象的monitor,則只能喚醒其中一個(gè)線程;
(notify()或者notifyAll()方法并不是真正釋放鎖,必須等到synchronized方法或者語(yǔ)法塊執(zhí)行完才真正釋放鎖)
4)調(diào)用notifyAll()方法能夠喚醒所有正在等待這個(gè)對(duì)象的monitor的線程,喚醒的線程獲得鎖的概率是隨機(jī)的,取決于cpu調(diào)度
例子1(錯(cuò)誤使用導(dǎo)致線程阻塞):三個(gè)線程,線程3先擁有sum對(duì)象的鎖,然后通過sum.notify()方法通知等待sum鎖的線程去獲得鎖,但是這個(gè)時(shí)候線程1,2并沒有處于wait()導(dǎo)致的阻塞狀態(tài),而是在synchronized方法塊處阻塞了,所以,這次notify()根本沒有通知到線程1,2。然后線程3正常結(jié)束,釋放掉sum鎖,這個(gè)時(shí)候,線程1就立刻獲得了sum對(duì)象的鎖(通過synchronized獲得),然后調(diào)用sum.wait()方法釋放掉sum的鎖,線程2隨后獲得了sum對(duì)象的線程鎖(通過synchronized獲得),這個(gè)時(shí)候線程1,2都處于阻塞狀態(tài),但是悲催的是,這之后再也沒有線程主動(dòng)調(diào)用sum.notify()或者notifyAll()方法顯示喚醒這兩個(gè)線程,所以程序阻塞.
public class CyclicBarrierTest {
public static void main(String[] args) throws Exception {
final Sum sum=new Sum();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (sum) {
System.out.println("thread3 get lock");
sum.sum();
sum.notifyAll(); //此時(shí)喚醒沒有作用,沒有線程等待
Thread.sleep(2000);
System.out.println("thread3 really release lock");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (sum) {
System.out.println("thread1 get lock");
sum.wait();//主動(dòng)釋放掉sum對(duì)象鎖
System.out.println(sum.total);
System.out.println("thread1 release lock");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (sum) {
System.out.println("thread2 get lock");
sum.wait(); //釋放sum的對(duì)象鎖,等待其他對(duì)象喚醒(其他對(duì)象釋放sum鎖)
System.out.println(sum.total);
System.out.println("thread2 release lock");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
class Sum{
public Integer total=0;
public void sum() throws Exception{
total=100;
Thread.sleep(5000);
}
}
運(yùn)行結(jié)果:
thread3 get lock
thread3 really release lock
thread2 get lock
thread1 get lock
//程序后面一直阻塞
例子2:還是上面程序,順序不同,把線程3放到最下面。最后線程1,2都因?yàn)闆]有再次獲得線程導(dǎo)致線程阻塞
運(yùn)行過程:
線程1先運(yùn)行獲得sum對(duì)象鎖(通過synchronized),但是隨后執(zhí)行了sum.wait()方法,主動(dòng)釋放掉了sum對(duì)象鎖,然后線程2獲得了sum對(duì)象鎖(通過synchronized),也通過sum.wait()失去sum的對(duì)象鎖,最后線程3獲得了sum對(duì)象鎖(通過synchronized),主動(dòng)通過sum.notify()通知了線程1或者2,假設(shè)是1,線程1重新通過notify()/notifyAll()的方式獲得了鎖,然后執(zhí)行完畢,隨后線程釋放鎖,然后這個(gè)時(shí)候線程2成功獲得鎖,執(zhí)行完畢。
public class CyclicBarrierTest {
public static void main(String[] args) throws Exception {
final Sum sum=new Sum();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (sum) {
System.out.println("thread1 get lock");
sum.wait();//主動(dòng)釋放sum對(duì)象鎖,等待喚醒
System.out.println(sum.total);
System.out.println("thread1 release lock");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (sum) {
System.out.println("thread2 get lock");
sum.wait(); //主動(dòng)釋放sum對(duì)象鎖,等待喚醒
System.out.println(sum.total);
System.out.println("thread2 release lock");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (sum) {
System.out.println("thread3 get lock");
sum.sum();
sum.notifyAll();//喚醒其他等待線程(線程1,2)
Thread.sleep(2000);
System.out.println("thread3 really release lock");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}
class Sum{
public Integer total=0;
public void sum() throws Exception{
total=100;
Thread.sleep(5000);
}
}
執(zhí)行結(jié)果
thread1 get lock
thread2 get lock
thread3 get lock
thread3 really release lock
100
thread2 release lock
100
thread1 release lock
經(jīng)典例題:生產(chǎn)者/消費(fèi)者問題
生產(chǎn)者(Productor)將產(chǎn)品交給店員(Clerk),而消費(fèi)者(Customer)從店員處取走產(chǎn)品,店員一次只能持有固定數(shù)量的產(chǎn)品(比如:20),如果生產(chǎn)者試圖生產(chǎn)更多的產(chǎn)品,店員會(huì)叫生產(chǎn)者停一下,如果店中有空位放產(chǎn)品了再通知生產(chǎn)者繼續(xù)生產(chǎn);如果店中沒有產(chǎn)品了,店員會(huì)告訴消費(fèi)者等一下,如果店中有產(chǎn)品了再通知消費(fèi)者來(lái)取走產(chǎn)品。
分析:
1、是否涉及到多線程的問題?是!生產(chǎn)者、消費(fèi)者
2、是否涉及到共享數(shù)據(jù)?有!考慮線程安全問題
3、此共享數(shù)據(jù)是誰(shuí)?產(chǎn)品的數(shù)量
4、是否涉及到線程的通信呢?存在生產(chǎn)者與消費(fèi)者的通信
class Clerk{//店員
int product;
public synchronized void addProduct(){//生產(chǎn)產(chǎn)品
if(product >= 20){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
product++;
System.out.println(Thread.currentThread().getName() + ":生產(chǎn)了第" + product + "個(gè)產(chǎn)品");
notifyAll();
}
}
public synchronized void consumeProduct(){//消費(fèi)產(chǎn)品
if(product <= 0){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
System.out.println(Thread.currentThread().getName() + ":消費(fèi)了第" + product + "個(gè)產(chǎn)品");
product--;
notifyAll();
}
}
}
class Producer implements Runnable{//生產(chǎn)者
Clerk clerk;
public Producer(Clerk clerk){
this.clerk = clerk;
}
public void run(){
System.out.println("生產(chǎn)者開始生產(chǎn)產(chǎn)品");
while(true){
try {
Thread.currentThread().sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
clerk.addProduct();
}
}
}
class Consumer implements Runnable{//消費(fèi)者
Clerk clerk;
public Consumer(Clerk clerk){
this.clerk = clerk;
}
public void run(){
System.out.println("消費(fèi)者消費(fèi)產(chǎn)品");
while(true){
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
clerk.consumeProduct();
}
}
}
public class TestProduceConsume {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producer p1 = new Producer(clerk);
Consumer c1 = new Consumer(clerk);
Thread t1 = new Thread(p1);//一個(gè)生產(chǎn)者的線程
Thread t3 = new Thread(p1);
Thread t2 = new Thread(c1);//一個(gè)消費(fèi)者的線程
t1.setName("生產(chǎn)者1");
t2.setName("消費(fèi)者1");
t3.setName("生產(chǎn)者2");
t1.start();
t2.start();
t3.start();
}
}