其實就是多個線程在操作同一個資源,但是操作的動作不同。
比如一個線程給一個變量指派,而另一個線程列印這個變量。
wait():将線程等待,釋放了cpu執行權,同時将線程對象存儲到線程池中。
notify():喚醒線程池中一個等待的線程,若線程池有多個等待的線程,則任意喚醒一個。
notifyall():喚醒線程池中,所有的等待中的線程。
這三個方法都要使用在同步中,因為要對持有鎖的線程進行操作。
比如,a鎖上的線程被wait了,那麼這個線程就進入了a鎖的線程池中,隻能被a鎖的notify喚醒,而不能被不同鎖的其他線程喚醒。
是以這三個方法要使用在同步中,因為隻有同步才具有鎖。
而鎖可以是任意對象,這三個方法被鎖調用,是以這三個方法可以被任意對象調用,是以這三個方法定義在object類中。
wait()和sleep()的差別:
wait():可以指定等待的時間,也可以不指定時間,如果不指定時間,就隻能被同一個鎖的notify或notifyall喚醒。wait時線程會釋放cpu執行權,并且會釋放鎖。
sleep():必須指定線程休眠的時間,線程休眠即暫停執行。時間到了,線程就自動蘇醒,恢複運作。sleep時線程會釋放執行權,但不釋放鎖。
線程的停止:
1,如果run()方法中定義了循環,可以用循環結束标記,跳出循環,則線程就停止了。
2,如果線程已被當機,讀不到循環結束标記,則需要通過thread類的interrupt方法中斷線程,讓線程重新獲得執行的資格,進而可以讀到循環結束标記,而結束線程。
3,setdaemon(true)方法将目前線程标記為守護線程,當運作的線程都是守護線程時,則java虛拟機退出。該方法必須在啟動線程前調用。
等待喚醒機制代碼,實作兩個線程交替執行,在控制台上交替列印兩個字元串。
等待和喚醒是同一個鎖r:
[java]
//兩個線程交替執行,在控制台交替列印兩串字元串。
class res{
string name;
string sex;
boolean flag = false; //等待喚醒機制
}
class input implements runnable{
private res r;
input(res r){
this.r = r;
}
public void run(){
int x = 0;
while(true){
synchronized(r){ //等待和喚醒,是同一個鎖。
if(r.flag) //等待喚醒機制,true則等待,false則執行
try{r.wait();}catch(exception e){} //線程等待,進入線程池
if(x == 0){
r.name = "luoqi";
r.sex = "man";
}
else{
r.name = "麗麗"; //指派時,指派了name還沒指派sex,就列印“lili----male”,加同步鎖,牢記同步前提。
r.sex = "女";
x = (x+1)%2;
r.flag = true; //等待喚醒機制
r.notify(); //任意喚醒線程池裡的一個被等待的線程 //等待喚醒機制
}
}
class output implements runnable{
output(res r){
if(!r.flag) //等待喚醒機制,false則等待,true則執行
system.out.println(r.name+"----"+r.sex);
r.flag = false; //等待喚醒機制
r.notify(); //喚醒input線程 //等待喚醒機制
class threadcommunication{
public static void main(string[] args){
res r = new res();
input in = new input(r);
output out = new output(r);
thread t1 = new thread(in);
thread t2 = new thread(out);
t1.start();
t2.start();
以上代碼中,把兩個同步代碼塊中的代碼,封裝成兩個同步方法,一個更改兩個字段的值,另一個列印兩個字段的值。
兩個同步方法寫在res類中,這樣同步鎖都是res.class位元組碼檔案,保證了等待和喚醒是同一個鎖:
boolean flag = false;
public synchronized void setres(string name,string sex){ //同步函數
if(this.flag) //flag為true,則線程等待進入線程池。
try{this.wait();}catch(exception e){}
this.name = name; //flag為false,則線程繼續執行。
this .sex = sex;
this.flag = true;
this.notify(); //任意喚醒線程池中一個等待的線程
public synchronized void getres(){
if(!this.flag) //flag為false,則線程等待進入線程池。
system.out.println(this.name+"----"+this.sex); //flag為true則繼續執行
this.flag = false;
this.notify(); //任意喚醒線程池中一個等待的線程
if(x == 0)
r.setres("luoqi","man");
else
r.setres("麗麗","女");
x = (x+1)%2;
r.getres();
class threadcommunication2{
使用線程間通信和線程同步解決生産者消費者問題。
while循環判斷标記和notifyall():
當出現多個生産者和多個消費者時,必須用while循環判斷标記,和notifyall喚醒全部線程。
對于多個生産者和消費者,為什麼要定義while判斷标記?
原因:讓被喚醒的線程再一次判斷标記。
為什麼使用notifyall()?
因為需要喚醒對方線程,因為notify是随機喚醒一個線程,容易出現隻喚醒本方線程的情況,導緻程式中的所有線程都等待。
代碼和注釋:
package mypkg;
class producerconsumerdemo{
resource r = new resource();
producer pro = new producer(r);
consumer con = new consumer(r);
thread t1 = new thread(pro); //兩個生産者線程
thread t2 = new thread(pro);
thread t3 = new thread(con); //兩個消費者線程
thread t4 = new thread(con);
t3.start();
t4.start();
class resource{
private string name;
private int count = 1;
private boolean flag = false;
public synchronized void set(string name){ //t1 t2
while(this.flag) //while循環判斷标記,讓被喚醒的線程再次判斷标記。标記為true則線程等待,為false則線程繼續執行
try{this.wait();} catch(exception e){}
this.name = name+"--"+count++;
system.out.println(thread.currentthread().getname()+"---生産者---"+this.name);
this.notifyall(); //必須喚醒對方,索性喚醒全部。因為有可能生産者喚醒了生産者,導緻有的商品被生産了但沒被消費。
public synchronized void get(){ //t3 t4
while(!this.flag)
try{this.wait();} catch(exception e){}
system.out.println(thread.currentthread().getname()+"---消費者---------"+this.name);
this.notifyall();
class producer implements runnable{
private resource r;
producer(resource r){
r.set("+商品+");
class consumer implements runnable{
consumer(resource r){
r.get();
運作結果:
jdk1.5 中提供了線程同步和線程間通信的更新解決方案,線程同步、線程間通信和等待喚醒機制都有了變化。
1,将同步synchronized替換成顯式的lock操作。
2,将同步鎖繼承自object類的wait()、notify()、notifyall()操作,替換成了condition對象的await()、signal()、signalall()操作。
3,該condition對象可以通過顯式的lock鎖來建立。
顯式的鎖機制,以及顯式的鎖對象上的等待喚醒操作機制,同時把等待喚醒進行封裝。
封裝完後,一個鎖可以對應多個condition,等待和喚醒必須是同一個condition對象調用。
jdk1.5之前,等待和喚醒必須是同一個鎖調用;
jdk1.5之後,等待和喚醒必須是同一個condition對象調用,而一個lock鎖可以建立多個condition對象。
進而,可以在生産者線程中,隻喚醒消費者的等待線程,即調用消費者的condition對象的喚醒操作。
lock接口,它的一個子類是reentrantlock,建立對象時new一個reentrantlock對象。
reentrantlock類的常用方法:
newcondition():建立鎖lock的condition對象,用來調用操作。
lock():擷取鎖。
unlock():釋放此鎖。
condition類的常用方法:
await(): 線程進入等待狀态,并抛出一個interruptedexception異常。
signal(): 喚醒一個等待線程。
signalall(): 喚醒所有等待線程。
import java.util.concurrent.locks.*;
class producerconsumerdemo2{
thread t1 = new thread(pro);
thread t3 = new thread(con);
final lock lock = new reentrantlock(); //建立一個鎖
final condition condition_pro = lock.newcondition(); //建立鎖lock的condition對象,用來操作生産者線程
final condition condition_con = lock.newcondition(); //建立鎖lock的condition對象,用來操作消費者線程
public void set(string name) throws interruptedexception { //t1 t2
lock.lock();
try{
while(this.flag)
condition_pro.await(); //await():線程等待,會抛出一個異常
this.name = name+"--"+count++;
system.out.println(thread.currentthread().getname()+"---生産者---"+this.name);
this.flag = true;
condition_con.signal(); //生産者中喚醒消費者
finally{
lock.unlock(); //釋放鎖的動作一定要執行,是以在finally中
public void get() throws interruptedexception { //t3 t4
while(!this.flag)
condition_con.await();
system.out.println(thread.currentthread().getname()+"---消費者---------"+this.name);
this.flag = false;
condition_pro.signal(); //消費者中喚醒生産者
lock.unlock();
try{
r.set("+商品+");
catch(interruptedexception e){}
r.get();
}
以前可以使用stop方法來停止線程,但是已經過時,那現在如何停止線程?
隻有一種方法:run方法結束。
開啟多線程運作,run方法内的運作代碼通常是循環結構,
隻要控制住循環,就可以讓run方法結束,也就是線程結束。
特殊情況:
當線程處于了當機狀态,就不會讀取到标記,那麼線程就不會結束。
當沒有指定的方式讓當機的線程恢複到運作狀态時,這是需要對當機進行清除。
強制讓現場恢複到運作狀态中來,這樣就可以操作标記讓線程結束。
thread類中提供該方法,interrupt()方法。
interrupt()方法是把線程從當機狀态恢複到運作狀态。
thread類中的setdaemon方法
setdaemon(boolean on):
on如果為 true,則将該線程标記為守護線程。
守護線程,當正在運作的線程都是守護線程時,java 虛拟機退出。該方法必須在啟動線程前調用。
jvm退出,守護線程在背景執行,了解為背景線程;全部為背景線程時,由前台轉為背景,jvm則退出。
代碼示例:
class stopthread implements runnable{
private boolean flag = true;
public synchronized void run(){
while(flag){
wait();
catch(interruptedexception e){
system.out.println(thread.currentthread().getname()+"....exception");
flag = false;
system.out.println(thread.currentthread().getname()+"....run");
public void changeflag(){
flag = false;
}
class stopthreaddemo{
stopthread st = new stopthread();
thread t1 = new thread(st);
thread t2 = new thread(st);
//t1.setdaemon(true); //守護線程,當正在運作的線程都是守護線程時,java 虛拟機退出。該方法必須在啟動線程前調用。
//t2.setdaemon(true);
int num = 0;
if(num++ == 60){
//st.changeflag();
t1.interrupt(); //中斷線程,讓線程從當機狀态恢複到運作狀态,這樣可以讀到flag标記進而結束線程。
t2.interrupt();
break; //跳出while循環
system.out.println(thread.currentthread().getname()+"......"+num);
system.out.println("over");
join():
當a線程執行到了b線程的join()方法時,那麼a線程就會等待;等b線程執行完,a才會執行。
join()可以用來臨時加入線程執行。
class demo implements runnable{
for(int x=0;x<70;x++){
system.out.println(thread.currentthread().getname()+"....."+x);
class joindemo{
public static void main(string[] args) throws exception{
demo d = new demo();
thread t1 = new thread(d);
thread t2 = new thread(d);
t1.join(); //t1線程向主線程索要cpu執行權,主線程阻塞,釋放cpu執行權,但釋放後t1和t2競争cpu執行權;
//t1線程執行結束後,主線程繼續。
for(int x=0; x<80; x++){
system.out.println("main...."+x);
線程優先級:
優先級高的線程,争奪cpu執行權的頻率就高,拿到cpu資源的可能性更大,
但并不是說優先級低的線程就不執行了。
thread類中定義了三個優先級常量:
max_priority 值為10,為最高優先級;
min_priority 值為1,為最低優先級;
norm_priority 值為5,預設優先級。
建立線程将繼承建立它的父線程的優先級,父線程是指執行建立新線程的語句所線上程,它可能是主線程,也可能是另一個自定義線程。
一般情況下,主線程具有預設優先級,為5。
可以通過getpriority()方法獲得線程的優先級,也可以通過setpriority()方法來設定優先級。
yield()方法:調用該方法後,可以使具有與目前線程相同優先級的線程有運作的機會。
可以臨時暫停目前線程,釋放cpu執行權,讓相同優先級的其他線程運作。
如果沒有相同優先級的線程,那麼yield()方法什麼也不做,目前線程繼續運作。
thread.yield(); //t1暫停,t2運作;t2暫停,t1運作。
//表現為t1、t2交替執行。
class yielddemo{
//system.out.println("main...."+x);
當某些代碼需要同時被執行時,就用單獨的線程進行封裝。
比如三個for循環同時運作,用多線程,高效,代碼示例:
class threadtest{ //三個for同時運作,用多線程,高效。
new thread(){ //匿名内部類
public void run(){
for(int x=0; x<50; x++){
system.out.println(thread.currentthread().getname()+"....."+x);
}.start();
for(int x=0; x<50; x++){
runnable r = new runnable(){ //匿名内部類
}
};
new thread(r).start();
}