------- android培訓、java培訓、期待與您交流! ----------
進程:是一個正在執行的程序。每個進程執行都有一個執行順序,該順序是一個執行路徑,或者叫做一個控制單元
線程:就是進程中的一個獨立的控制單元。線程控制着進程的執行。
一個進程中 至少有一個線程
java VM啓動的時候會有一個進程java.exe,該進程中至少有一個線程負責java程序的執行,而且這個線程運行的代碼存在於main方法中,該線程稱爲主線程。
其實更多細節說明jvm啓動不止一個線程,還有負責垃圾回收機制的線程。
Thread類就是對線程這類事物的描述。
1.創建線程的第一種方法
繼承Thread類;
複寫run方法;
覆蓋run方法的原因:Thread類定義了一個功能,用於存儲線程要運行的代碼,該存儲功能就是run方法
目的:將自定義的代碼存儲在run方法中,讓I線程運行
使用線程的start方法啓動線程,調用run方法
class Demo extends Thread //繼承Thread類,創建線程
{
public void run() //複寫run方法
{
for(int x=0;x<10;x++)
System.out.println("demo run----"+x);
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d=new Demo(); //建立一個對象,同時也就創建了一個線程
d.start(); //調用start()方法,啓動線程,調用run方法
for(int x=0;x<10;x++)
{
System.out.println("Hello Word----------"+x);
}
}
}
運行結果:
發現運行結果每次都不一致,因爲多個線程都在獲取cpu的執行權,cpu執行到哪個線程,就運行哪個線程。在某一時刻,只能有一個程序在運行(多核除外)。
以上體現出了線程的一個特性,即隨機性,誰搶到誰運行,運行時間由cpu決定
練習:創建兩個現場,和主線程交替運行
class Demo extends Thread //繼承Thread類
{
Demo(String name) //初始化構造函數,傳遞線程名稱
{
super(name); //自定義線程名的方法,調用父類線程名
}
public void run()
{
for(int x=0;x<60;x++)
{
System.out.println(Thread.currentThread().getName()+"T run---"+x); //獲取當前線程名
}
}
}
class ThreadTest
{
public static void main(String[] args)
{
Demo d=new Demo("zhangsan"); //創建線程,並傳遞線程名
Demo e=new Demo("lisi");
d.start();
e.start();
for(int x=0;x<60;x++)
{
System.out.println("main run-------------"+x);
}
}
}
線程都有自己的默認名;格式:Thread-數字,數字從0開始
發現Thread構造方法初始化的時候就有名稱Thread(String name);線程Thread中有getName(),setName()方法來獲取線程名,所以只需要調用super(name)就可以獲取父類Thread來獲取線程名static Thread currentThread():獲取當前線程對象
設置線程名稱:setName()或者構造函數
練習:簡單的買票程序:多個窗口同時買票
class Ticket implements Runnable//extends Thread //繼承Thread類
{
private <span style="color:#ff0000;">static</span> int tick=100; //初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
public void run() //複寫run方法
{
while(true)
{
if(tick>0) //當還有票的時候
System.out.println(Thread.currentThread().getName()+"sale:"+tick--); //獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
//創建線程
Ticket t1=new Thread();
Ticket t2=new Thread();
Ticket t3=new Thread();
Ticket t4=new Thread();
//啓動線程,調用run方法
t1.start();
t2.start();
t3.start();
t4.start();
}
}
但是使用靜態讓票數固定,會浪費資源,由此引入線程的第二種創建方法:
聲明實現Runnable接口;
覆蓋Runnable接口中的run方法;
將線程要運行的代碼放在該方法中
通過Thread類建立線程對象;
將Runnable接口的子類對象作爲實際參數傳入Thread類;
實現此步驟的原因:自定義的run方法所屬的對象是Runnable接口的子類對象,所以要讓線程去指定某個對象的run方法,就必須明確該run方法所屬對象
調用Thread類的start方法開啓線程,並調用Runnable接口的run方法
還是買票的例子:
class Ticket implements Runnable //實現Runnable接口
{
private int tick=100; //初始化成員變量:票的總數
public void run() //複寫run方法
{
while(true)
{
if(tick>0) //當還有票的時候
System.out.println(Thread.currentThread().getName()+"sale:"+tick--); //獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
}
}
}
class TicketDemo
{
public static void main(String[] args)
{
/*
//創建線程
Ticket t1=new Ticket();
Ticket t2=new Ticket();
Ticket t3=new Ticket();
Ticket t4=new Ticket();
//啓動線程,調用run方法
t1.start();
t2.start();
t3.start();
t4.start();
*/
Ticket t=new Ticket(); //創建一個Ticket對象
//創建線程,並將線程與run()方法相關聯,將實現Runnable接口的類對象作爲實際參數傳入線程對象中
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
//運行線程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
運行結果:
線程也運行起來了
那麼繼承類與實現類有什麼區別呢?
實現方式避免了單繼承的侷限性。類可以在繼承的基礎上實現接口,功能得以拓展。在定義線程時,最好使用實現方式
線程代碼存在實現Runnable接口的子類的run方法中;而在繼承中,線程代碼存在於Thread子類的run方法中
在上面賣票的例子中通過傳入對象作爲參數,使各個線程運行的同時還可以保持票的總數不變
在售票的例子中,讓線程停頓10毫秒,發現結果出現了負數。多線程的運行出現了安全問題
class Ticket implements Runnable //實現Runnable接口
{
private static int tick=100; //初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
public void run() //複寫run方法
{
while(true)
{
if(tick>0) //當還有票的時候
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"sale:"+tick--); //獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
}
}
}
class TicketDemo1
{
public static void main(String[] args)
{
/*
//創建線程
Ticket t1=new Ticket();
Ticket t2=new Ticket();
Ticket t3=new Ticket();
Ticket t4=new Ticket();
//啓動線程,調用run方法
t1.start();
t2.start();
t3.start();
t4.start();
*/
Ticket t=new Ticket(); //創建一個Ticket對象
//創建線程,並將線程與run()方法相關聯
Thread t1=new Thread(t);
Thread t2=new Thread(t);
Thread t3=new Thread(t);
Thread t4=new Thread(t);
//運行線程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
問題的原因:當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒執行完,另一個線程參與進來執行,倒是共享數據的錯誤
解決辦法:對於多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與
對於次安全問題,使用同步代碼塊處理:
synchronized(對象)
{
需要被同步的代碼塊
}
之後票數就正常了
對象如同鎖,持有鎖的線程可以在同步中執行;沒有持有鎖的線程即使獲取cpu執行權,也進不去,因爲沒有持有鎖
同步的前提:1.必須要有兩個或兩個以上的線程;2.必須是多個線程使用同一個鎖
同步的好處:可以解決多線程的安全問題
弊端:多個線程需要判斷鎖,較爲消耗資源
練習:銀行金庫,兩個客戶分別存入300元,分3次,每次存100
如何找問題:明確那些代碼是多線程運行代碼;明確共享數據;明確多線程運行代碼中哪些語句是操作共享數據
引入同步函數:把同步作爲修飾符傳給函數
class Bank //對銀行進行描述
{
Object obj=new Object();
private int sum; //引用成員變量,銀行金庫錢的總數
public synchronized void add(int n) //存入錢之後,金庫內錢增加,計算總和,同步函數
{
//synchronized(obj)
//{
sum=sum+n;
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println("sum="+sum);
//}
}
}
class Cus implements Runnable //實現Runnable接口
{
private Bank b=new Bank(); //引用Bank對象作爲成員變量
public void run() //複寫run方法
{
for(int x=0;x<3;x++) //分爲3次存錢
{
b.add(100); //銀行金庫總存款增加
}
}
}
class BankDemo
{
public static void main(String[] args)
{
Cus c=new Cus(); //新建實現Runnable接口的類對象
//創建線程對象,並將實現Runnable接口的類對象作爲參數傳入
Thread t1=new Thread(c);
Thread t2=new Thread(c);
//啓動線程,調用Runnable接口子類的run方法
t1.start();
t2.start();
}
}
那麼同步函數用的是哪個鎖呢?函數需要被對象調用,那麼函數都有一個所屬對象調用,即this.所以同步函數使用的鎖是this
接下來就買票的例子進行驗證
class Ticket implements Runnable //實現Runnable接口
{
private static int tick=100; //初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
Object obj=new Object();
boolean flag=true;
public void run() //複寫run方法
{
if(flag)
{
while(true)
{
synchronized(obj) //傳入對象設爲上帝類Object
{ //這裏tick就是共享數據,所以在操作tick的代碼上使用同步代碼塊
if(tick>0) //當還有票的時候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+" code-"+tick--); //獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
}
}
}
}
else
{
while(true)
{
show();
}
}
}
public synchronized void show()<span style="white-space:pre"> </span>//同步函數調用的是使用該方法的對象的鎖,即this
{
if(tick>0) //當還有票的時候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"show===:"+tick--); //獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
}
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Ticket t=new Ticket(); //創建一個Ticket對象
//創建線程,並將線程與run()方法相關聯
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//運行線程
t1.start();
//主函數瞬間先執行完,如此標誌位是false,再執行另外兩個線程,此時兩個線程都會執行run方法中的else語句那麼就只有同步函數在運行了,爲了避免這種情況,在此處讓主線程停10毫秒,從而能使其中一個線程運行同步代碼塊,而另一個則運行同步函數
try
{
Thread.sleep(10);
}
catch(Exception e)
{}
t.flag=false; //將標誌更改,使得t2運行同步函數
t2.start();
}
}
發現結果中有出售0號票的
原因是共享數據用的不是同一個鎖,第一個線程用的是obj的鎖,第二個線程用的是this鎖
將代碼塊的鎖也改爲this
class Ticket implements Runnable //實現Runnable接口
{
private static int tick=100; //初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
Object obj=new Object();
boolean flag=true;
public void run() //複寫run方法
{
if(flag)
{
while(true)
{
synchronized(<span style="color:#ff0000;">this</span>)
{ //這裏tick就是共享數據,所以在操作tick的代碼上使用同步代碼塊
if(tick>0) //當還有票的時候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+" code-"+tick--); //獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
}
}
}
}
else
{
while(true)
{
show();
}
}
}
public synchronized void show()
{
if(tick>0) //當還有票的時候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"show===:"+tick--); //獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
}
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Ticket t=new Ticket(); //創建一個Ticket對象
//創建線程,並將線程與run()方法相關聯
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//運行線程
t1.start();
//主函數瞬間先執行完,如此標誌位是false,再執行另外兩個線程,此時兩個線程都會執行run方法中的else語句那麼就只有同步函數在運行了,爲了避免這種情況,在此處讓主線程停10毫秒,從而能使其中一個線程運行同步代碼塊,而另一個則運行同步函數
try
{
Thread.sleep(10);
}
catch(Exception e)
{}
t.flag=false; //將標誌更改,使得t2運行同步函數
t2.start();
}
}
同步函數和同步代碼塊都使用的是this鎖,如此就沒有0號票了
靜態同步函數:如果同步函數被靜態修飾後,使用的鎖是什麼呢?
class Ticket implements Runnable //實現Runnable接口
{
private static int tick=100; //初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
Object obj=new Object();
boolean flag=true;
public void run() //複寫run方法
{
if(flag)
{
while(true)
{
synchronized(<span style="color:#ff0000;">this</span>)
{ //這裏tick就是共享數據,所以在操作tick的代碼上使用同步代碼塊
if(tick>0) //當還有票的時候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+" code-"+tick--); //獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
}
}
}
}
else
{
while(true)
{
show();
}
}
}
public static synchronized void show()
{
if(tick>0) //當還有票的時候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"show===:"+tick--); //獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
}
}
}
class TicketDemo3
{
public static void main(String[] args)
{
Ticket t=new Ticket(); //創建一個Ticket對象
//創建線程,並將線程與run()方法相關聯
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//運行線程
t1.start();
//主函數瞬間先執行完,如此標誌位是false,再執行另外兩個線程,此時兩個線程都會執行run方法中的else語句那麼就只有同步函數在運行了,爲了避免這種情況,在此處讓主線程停10毫秒,從而能使其中一個線程運行同步代碼塊,而另一個則運行同步函數
try
{
Thread.sleep(1);
}
catch(Exception e)
{}
t.flag=false; //將標誌更改,使得t2運行同步函數
t2.start();
}
}
運行結果有0號票,說明用的不是同一個鎖,同步函數的鎖不在是this了,因爲靜態中不存在this
靜態在內存是,內存中沒有本類對象,但是一定有該類對象對應的字節碼文件對象
類名.class,該對象的類型是Class
class Ticket implements Runnable //實現Runnable接口
{
private static int tick=100; //初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
Object obj=new Object();
boolean flag=true;
public void run() //複寫run方法
{
if(flag)
{
while(true)
{
synchronized(<span style="color:#ff0000;">Ticket.class</span>)
{ //這裏tick就是共享數據,所以在操作tick的代碼上使用同步代碼塊
if(tick>0) //當還有票的時候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+" code-"+tick--); //獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
}
}
}
}
else
{
while(true)
{
show();
}
}
}
public static synchronized void show()
{
if(tick>0) //當還有票的時候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"show===:"+tick--); //獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
}
}
}
class TicketDemo3
{
public static void main(String[] args)
{
Ticket t=new Ticket(); //創建一個Ticket對象
//創建線程,並將線程與run()方法相關聯
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//運行線程
t1.start();
//主函數瞬間先執行完,如此標誌位是false,再執行另外兩個線程,此時兩個線程都會執行run方法中的else語句那麼就只有同步函數在運行了,爲了避免這種情況,在此處讓主線程停10毫秒,從而能使其中一個線程運行同步代碼塊,而另一個則運行同步函數
try
{
Thread.sleep(1);
}
catch(Exception e)
{}
t.flag=false; //將標誌更改,使得t2運行同步函數
t2.start();
}
}
發現程序又安全了,所以靜態的同步方法使用的鎖是該方法所在類的字節碼文件對象,類名.class
單例設計模式:懶漢式的同步
先複習下餓漢式
class Single
{
private static final Single s=new Single();<span style="white-space:pre"> </span>//一開始就初始化對象值
private Single()
{}
public static Single getInstance()
{
return s;
}
}
//懶漢式
class Single
{
private static Single s=null;
private Single()
{}
public static Single getInstance()
{
if(s==null)
s=new Single();
return s;
}
}
對於懶漢式,如果同時有多個線程在調用該類,就會出現安全問題,一個在判斷,一個在賦值,不能保證對象的唯一性,所以引入同步,
class Single
{
private static Single s=null; //引入成員變量
private Single()
{}
public static Single getInstance() //創建獲取實例對象的方法
{
if(s==null) //使用雙重判斷,提高效率
{
synchronized(Single.class)
if(s==null)
s=new Single();
return s;
}
}
}
使用雙重判斷,卻比較麻煩,所以平常使用最好使用餓漢式,餓漢式只有一句話,不存在同步問題
死鎖:通常情況下是同步中嵌套同步,而鎖不同
class Ticket implements Runnable //實現Runnable接口
{
private static int tick=100; //初始化成員變量:票的總數,在此使用靜態,使票的總數保持在100張
Object obj=new Object();
boolean flag=true;
public void run() //複寫run方法
{
if(flag)
{
while(true)
{
synchronized(obj) //同步代碼塊中調用同步函數
{
show();
}
}
}
else
{
while(true)
{
show();
}
}
}
public synchronized void show() //同步函數中包含同步代碼塊,調用的是this鎖
{
synchronized(obj)
{
if(tick>0) //當還有票的時候
{
try
{
Thread.sleep(10);
}
catch(Exception e)
{
}
System.out.println(Thread.currentThread().getName()+"show===:"+tick--); //獲取當前線程名,並且票越賣越少,所以遞減,記錄賣的哪一張票
}
}
}
}
class DeadLockDemo2
{
public static void main(String[] args)
{
Ticket t=new Ticket(); //創建一個Ticket對象
//創建線程,並將線程與run()方法相關聯
Thread t1=new Thread(t);
Thread t2=new Thread(t);
//運行線程
t1.start();
//主函數瞬間先執行完,如此標誌位是false,再執行另外兩個線程,此時兩個線程都會執行run方法中的else語句那麼就只有同步函數在運行了,爲了避免這種情況,在此處讓主線程停10毫秒,從而能使其中一個線程運行同步代碼塊,而另一個則運行同步函數
try
{
Thread.sleep(1);
}
catch(Exception e)
{}
t.flag=false; //將標誌更改,使得t2運行同步函數
t2.start();
}
}
發現沒運行一會兒變沒有了,發生了死鎖
寫出一個死鎖程序
class Test implements Runnable //實現Runnable接口
{
private boolean flag;
Test(boolean flag)
{
this.flag=flag;
}
public void run() //複寫run方法
{
if(flag)
{
while(true)
{
synchronized(MyLock.locka) //locka鎖嵌套lockb鎖
{
System.out.println("if locka");
synchronized(MyLock.lockb)
{
System.out.println("if lockb");
}
}
}
}
else
{
while(true)
{
synchronized(MyLock.lockb) //lockb中嵌套locka鎖
{
System.out.println("else lockb");
synchronized(MyLock.locka)
{
System.out.println("else locka");
}
}
}
}
}
}
class MyLock //創建一個鎖對象
{
static Object locka=new Object();
static Object lockb=new Object();
}
class DeadLockDemo
{
public static void main(String[] args)
{
//創建線程,傳入實現Runnable接口的類作爲參數
Thread t1=new Thread(new Test(true));
Thread t2=new Thread(new Test(false));
//啓動線程
t1.start();
t2.start();
}
}
只循環了一次便終止了,發生了死鎖
線程間通訊
建立一個共享資源,不斷交替傳入兩個姓名和性別,然後打印出來
/*
練習:不斷的存入兩個人名,不斷的輸出打印
*/
class InputOutputDemo1
{
public static void main(String[] args)
{
Res r=new Res(); //建立資源對象
//創建線程第二步,創建Runnable接口的子類對象
Input in=new Input(r); //建立輸入對象,並將資源對象作爲參數傳入
Output out=new Output(r); //建立輸出對象,並將新建的資源對象傳入
//創建線程第三步,新建線程,並將實現Runnable的子類對象傳入
Thread t1=new Thread(in);
Thread t2=new Thread(out);
t1.start();
t2.start();
}
}
class Res //建立共有資源類
{
//包含姓名和性別
String name;
String sex;
}
class Input implements Runnable //存入線程,將姓名和性別存入資源中
{
private Res r; //創建資源類型的成員變量
Input(Res r) //構造函數初始化時就獲取資源對象
{
this.r=r;
}
public void run() //複寫run方法
{
int x=0;
while(true)
{
synchronized(r)<span style="white-space:pre"> </span>//加入同步,避免第一個性別還沒打印,第二個性別就先打印了,此處尋找同一個鎖,發現r就是共享對象
{
if(x==0)
{
r.name="mike";
r.sex="男";
}
else
{
r.name="lili";
r.sex="女女女女女女女女女";
}
x=(x+1)%2; //通過x的值的變換,來切換兩個名字互相切換
}
}
}
}
class Output implements Runnable
{
private Res r; //與傳入線程類似,創建資源型成員變量
Output(Res r) //初始化構造函數的時候獲取資源對象
{
this.r=r;
}
public void run() //複寫run方法
{
while(true)
{
synchronized(r)<span style="white-space:pre"> </span><span style="font-family: Arial, Helvetica, sans-serif;">//加入同步,避免第一個性別還沒打印,第二個性別就先打印了,此處尋找同一個鎖,發現r就是共享對象</span><span style="white-space:pre">
</span>
{
System.out.println(r.name+"=="+r.sex);
}
}
}
}
注意,在上面中,對姓名和性別賦值,打印都在不斷操作共同數據,所以在輸出語句中也要加入鎖
此時雖然不會有性別和姓名錯亂,但是希望的結果是兩個姓名和年齡交替打印,而這裏可能輸入線程出了同步之後,沒有被輸出縣城搶到執行權,繼續被輸入線程搶到執行權,然後繼續覆蓋之前的姓名,不斷重複打印一個姓名和性別
此處引入等待喚醒機制
/*
練習:不斷的存入兩個人名,不斷的輸出打印
*/
class InputOutputDemo1
{
public static void main(String[] args)
{
Res r=new Res(); //建立資源對象
//創建線程第二步,創建Runnable接口的子類對象
Input in=new Input(r); //建立輸入對象,並將資源對象作爲參數傳入
Output out=new Output(r); //建立輸出對象,並將新建的資源對象傳入
//創建線程第三步,新建線程,並將實現Runnable的子類對象傳入
Thread t1=new Thread(in);
Thread t2=new Thread(out);
t1.start();
t2.start();
}
}
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() //複寫run方法
{
int x=0;
while(true)
{
synchronized(r)
{
if(r.flag)
{
try{
r.wait(); //如果標誌位爲真,就代表資源中姓名和年齡有值,讓線程進入等待狀態,讓輸出線程獲得執行權
}
catch(Exception e){}
}
if(x==0)
{
r.name="mike";
r.sex="男";
}
else
{
r.name="lili";
r.sex="女女女女女女女女女";
}
x=(x+1)%2; //通過x的值的變換,來切換兩個名字互相切換
r.flag=true; //賦值之後,將標誌位修改爲真
r.notify(); //喚醒等待的線程,兩個線程都有執行權,如果此線程搶到執行權,會返回上面判斷,然後就會等待
}
}
}
}
class Output implements Runnable
{
private Res r; //與傳入線程類似,創建資源型成員變量
Output(Res r) //初始化構造函數的時候獲取資源對象
{
this.r=r;
}
public void run() //複寫run方法
{
while(true)
{
synchronized(r)
{
if(!r.flag) //如果標誌位爲假,則是姓名性別的值已經打印過了爲空了
{
try{
r.wait(); //就讓打印線程等待,輸入線程運行
}
catch(Exception e){}
}
System.out.println(r.name+"=="+r.sex);
r.flag=false; //打印完之後,將標誌位的值修改,
r.notify(); //將等待的線程喚醒,即喚醒輸入線程;如果輸出線程再搶到執行權,就會再回到上面判斷,發現爲假,就會等待,然後只有輸入線程執行了
}
}
}
}
wait();notify();notifyAll() 都使用在同步中,因爲要對持有監視器的線程操作,所以都使用在同步中,因爲只有監視器中才有鎖 在上面,因爲線程存在嵌套,所以使用wait,notify方法的時候需要標明是調用對象鎖r,即r.wart,r.notify;
上述方法都在Object中,因爲等待和喚醒必須是同一個鎖,而鎖可以是任意一個對象,所以是定義在Object中的
下面可以對代碼進行優化
/*
練習:不斷的存入兩個人名,不斷的輸出打印
*/
class InputOutputDemo1
{
public static void main(String[] args)
{
Res r=new Res(); //建立資源對象
//創建線程第二步,創建Runnable接口的子類對象
Input in=new Input(r); //建立輸入對象,並將資源對象作爲參數傳入
Output out=new Output(r); //建立輸出對象,並將新建的資源對象傳入
//創建線程第三步,新建線程,並將實現Runnable的子類對象傳入
Thread t1=new Thread(in);
Thread t2=new Thread(out);
t1.start();
t2.start();
}
}
class Res //建立共有資源類
{
//包含姓名和性別
private String name;
private String sex;
private boolean flag=false; //添加一個標誌位,來維持兩個線程分別進行
public synchronized void set(String name,String sex)
{
if(flag)
{
try{
this.wait(); //如果標誌位爲真,就代表資源中姓名和年齡有值,讓線程進入等待狀態,讓輸出線程獲得執行權
}
catch(Exception e){}
}
this.name=name;
this.sex=sex;
flag=true;
this.notify();<span style="white-space:pre"> </span>//喚醒鎖對象監視的等待線程,此處即輸出線程
}
public synchronized void printout()
{
if(!flag)
{
try{
this.wait(); //如果標誌位爲真,就代表資源中姓名和年齡有值,讓線程進入等待狀態,讓輸出線程獲得執行權
}
catch(Exception e){}
}
System.out.println(this.name+"=="+this.sex);
flag=false;
this.notify();<span style="white-space:pre"> </span>//喚醒鎖對象監視的等待的線程,此處即喚醒輸入線程
}
}
class Input implements Runnable //存入線程,將姓名和性別存入資源中
{
private Res r; //創建資源類型的成員變量
Input(Res r) //構造函數初始化時就獲取資源對象
{
this.r=r;
}
public void run() //複寫run方法
{
int x=0;
while(true)
{
if(x==0)
r.set("mike","男");
else
r.set("麗麗","女女女女女女女女女");
x=(x+1)%2; //通過x的值的變換,來切換兩個名字互相切換
}
}
}
class Output implements Runnable
{
private Res r; //與傳入線程類似,創建資源型成員變量
Output(Res r) //初始化構造函數的時候獲取資源對象
{
this.r=r;
}
public void run() //複寫run方法
{
while(true)
{
r.printout();
}
}
}
練習:多個生產者和消費者同時運行,但要保持生產一個產品就要消費一個產品
class BCTest
{
public static void main(String[] args)
{
Res r=new Res(); //創建資源對象
//創建生產消費者對象,並把資源對象作爲參數傳入
Pro p=new Pro(r);
Consumersumersumer c=new Consumersumersumer(r);
//創建線程
Thread t1=new Thread(p);
Thread t2=new Thread(p);
Thread t3=new Thread(c);
Thread t4=new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Res
{
private String name; //創建產品名稱變量
private int count=1; //產品編號變量
private boolean flag; //標誌位
public synchronized void set(String name) //構造生產者線程的調用方法
{
while(flag) //使用whil循環,使得被喚醒的線程每次都要先判斷標誌,避免生產一個還沒消費就被覆蓋掉了
{
try
{
this.wait(); //如果標誌位爲真,則讓生產者等待,讓消費者消費
}
catch(Exception e)
{
}
}
this.name=name+"=="+count++; //產品一有名稱就自帶編號
System.out.println(Thread.currentThread().getName()+"生產者=========="+this.name);
flag=true; //修改標誌位
this.notifyAll(); //喚醒所有等待中的線程
}
public synchronized void out() //構造消費者線程的調用方法
{
while(!flag)
{
try
{
this.wait();
}
catch(Exception e)
{
}
}
System.out.println(Thread.currentThread().getName()+"消費者"+this.name);
flag=false; //打印完成後修改標誌位
this.notifyAll(); //喚醒處於等待狀態的所有線程
}
}
class Pro implements Runnable //創建生產者類
{
private Res r; //創建私有變量爲資源類型的
Pro(Res r) //初始化構造函數
{
this.r=r;
}
public void run()
{
while(true)
r.set("商品"); //調用生產者方法
}
}
class Consumersumersumer implements Runnable //創建消費者類
{
private Res r; //創建私有資源類型的變量
Consumersumersumer(Res r) //初始化構造函數獲取資源對象
{
this.r=r;
}
public void run()
{
while(true)
r.out(); //調用消費者方法
}
}
JDK5.0之後,synchronized 被Lock(接口)替代;調用其方法newCondition()可以新建一個Condition對象,包含notify,wait,notifyAll方法
並且可以建立多個Condition對象,對於上述例子,可以新建兩個分別代表生產者和消費者對應的Condition對象
import java.util.concurrent.locks.*;
class CPTest
{
public static void main(String[] args)
{
Res r=new Res(); //創建資源對象
//創建生產消費者對象,並把資源對象作爲參數傳入
Pro p=new Pro(r);
Consumersumersumer c=new Consumersumersumer(r);
//創建線程
Thread t1=new Thread(p);
Thread t2=new Thread(p);
Thread t3=new Thread(c);
Thread t4=new Thread(c);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Res
{
private String name; //創建產品名稱變量
private int count=1; //產品編號變量
private boolean flag; //標誌位
private Lock lock=new ReentrantLock(); //建立鎖的對象,取代了舊版中的同步
private Condition cond_con=lock.newCondition(); //condition中封裝了wait,notify,notifyAll方法,所以建立此對象可以調用其中的方法
private Condition cond_pro=lock.newCondition(); //建立消費者對應的Condition對象
public void set(String name) throws InterruptedException //構造生產者線程的調用方法
{
lock.lock(); //加上鎖
try
{
while(flag) //使用whil循環,使得被喚醒的線程每次都要先判斷標誌,避免生產一個還沒消費就被覆蓋掉了
{
cond_con.await(); //如果標誌位爲真,則讓生產者等待,讓消費者消費
}
this.name=name+"=="+count++; //產品一有名稱就自帶編號
System.out.println(Thread.currentThread().getName()+"生產者=========="+this.name);
flag=true; //修改標誌位
cond_pro.signal(); //喚醒等待的線程
}
finally
{
lock.unlock(); //解鎖
}
}
public void out() throws InterruptedException //構造消費者線程的調用方法
{
lock.lock();
try{
while(!flag)
{
cond_pro.await();
}
System.out.println(Thread.currentThread().getName()+"消費者"+this.name);
flag=false; //打印完成後修改標誌位
cond_con.signal(); //喚醒等待線程
}
finally
{
lock.unlock(); //解除鎖,釋放資源
}
}
}
class Pro implements Runnable //創建生產者類
{
private Res r; //創建私有變量爲資源類型的
Pro(Res r) //初始化構造函數
{
this.r=r;
}
public void run()
{
while(true)
try
{
r.set("商品"); //調用生產者方法
}
catch (InterruptedException e)
{
}
}
}
class Consumersumersumer implements Runnable //創建消費者類
{
private Res r; //創建私有資源類型的變量
Consumersumersumer(Res r) //初始化構造函數獲取資源對象
{
this.r=r;
}
public void run()
{
while(true)
try
{
r.out(); //調用消費者方法
}
catch (InterruptedException e)
{
}
}
}
同時,新版本之後,要想停止線程只有一種方法,就是結束run方法
在開啓多線程運行的時候,通常會在運行代碼中使用循環結構,只要控制住循環結構就可以終止線程