Thread類的子類: MyThread java
//1.建立一個Thread類的子類 public class MyThread extends Thread{ //2.在Thread類的子類中重寫Thread類中的run方法,設置線程任務(開啓線程要作什麼?) @Override public void run() { for (int i = 0; i <20 ; i++) { System.out.println("run:"+i); } } }
主線程: MyThread 程序員
public class MainThread { public static void main(String[] args) { //3.建立Thread類的子類對象 MyThread mt = new MyThread(); //4.調用Thread類中的方法start方法,開啓新的線程,執行run方法 mt.start();
for (int i = 0; i <20 ; i++) { System.out.println("main:"+i); } } }
結果:隨機性打印編程
main:0 run:0 main:1 run:1 main:2 run:2 main:3 run:3 main:4 main:5 。。。。
原理:安全
一、JVM執行 MainThread類的main 方法時,知道OS開闢了一條main方法通向CPU的路徑。服務器
這個路徑叫作main線程,主線程。CPU經過這個路徑能夠執行main方法。多線程
二、JVM執行到 mt.start();時,開闢了一條通向CPU的新路徑來 執行run方法。併發
三、對於CPU而言,就有了兩條執行的路徑,CPU就有了選擇權,咱們控制不了CPU,異步
兩個線程,main線程和,新的 MyThread的新線程,一塊兒搶奪CPU的執行權ide
(執行時間),誰搶到誰執行。函數
線程的內存解析圖:
實現Runable接口的子類:MyRunbale
//1.建立一個Runnable接口的實現類 public class MyRunnable implements Runnable { //2.在實現類中重寫Runnable接口的run方法,設置線程任務 @Override public void run() { for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } }
主線程: MyThread
public class MainThread { public static void main(String[] args) { //3.建立一個Runnable接口的實現類對象 MyRunnable run = new MyRunnable(); //4.建立Thread類對象,構造方法中傳遞Runnable接口的實現類對象 //Thread t = new Thread(run);//打印線程名稱 Thread t = new Thread(run);//打印HelloWorld //5.調用Thread類中的start方法,開啓新的線程執行run方法 t.start(); for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getName()+"-->"+i); } } }
結果:隨機性打印
main-->0
Thread-0-->0
main-->1
Thread-0-->1
main-->2
Thread-0-->2
main-->3
Thread-0-->3
main-->4
Thread-0-->4
main-->5
Thread-0-->5
main-->6
Thread-0-->6
main-->7
Thread-0-->7
原理和內存和實現Thread相識。
實現Runnable接口建立多線程程序的好處: 1.避免了單繼承的侷限性 一個類只能繼承一個類(一我的只能有一個親爹),類繼承了Thread類就不能繼承其餘的類 實現了Runnable接口,還能夠繼承其餘的類,實現其餘的接口 2.加強了程序的擴展性,下降了程序的耦合性(解耦) 實現Runnable接口的方式,把設置線程任務和開啓新線程進行了分離(解耦) 實現類中,重寫了run方法:用來設置線程任務 建立Thread類對象,調用start方法:用來開啓新線程
匿名內部類做用:簡化代碼
把子類繼承父類,重寫父類的方法,建立子類對象合一步完成
把實現類實現類接口,重寫接口中的方法,建立實現類對象合成一步完成
匿名內部類的最終產物:子類/實現類對象,而這個類沒有名字
實現代碼:
/* 匿名內部類方式實現線程的建立 匿名:沒有名字 內部類:寫在其餘類內部的類 格式: new 父類/接口(){ 重複父類/接口中的方法 }; */ public class InnerClassThread { public static void main(String[] args) { //線程的父類是Thread // new MyThread().start(); new Thread(){ //重寫run方法,設置線程任務 @Override public void run() { for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getName()+"-->"+"NOT_Copy"); } } }.start(); //線程的接口Runnable //Runnable r = new RunnableImpl();//多態 Runnable r = new Runnable(){ //重寫run方法,設置線程任務 @Override public void run() { for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getName()+"-->"+"程序員"); } } }; new Thread(r).start(); //簡化接口的方式 new Thread(new Runnable(){ //重寫run方法,設置線程任務 @Override public void run() { for (int i = 0; i <20 ; i++) { System.out.println(Thread.currentThread().getName()+"-->"+"MWW"); } } }).start(); } }
一、單線程不會出現線程安全問題
二、多個線程,沒有訪問共享資源,也不會產生線程安全問題
三、多個線程,且訪問了共享資源,就會產生線程安全問題。
代碼示例:
線程實現類:
/* 實現賣票案例 */ public class RunnableImpl implements Runnable{ //定義一個多個線程共享的票源 private int ticket = 100;
//設置線程任務:賣票 @Override public void run() { //使用死循環,讓賣票操做重複執行 while(true){ //先判斷票是否存在 if(ticket>0){ try { Thread.sleep(10); //提升安全問題出現的機率,讓程序睡眠 } catch (InterruptedException e) { e.printStackTrace(); } //票存在,賣票 ticket-- System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票"); ticket--; } } } }
MainThread類:
/* 模擬賣票案例 建立3個線程,同時開啓,對共享的票進行出售 */ public class MainThread { public static void main(String[] args) { //建立Runnable接口的實現類對象 RunnableImpl run = new RunnableImpl(); //建立Thread類對象,構造方法中傳遞Runnable接口的實現類對象 Thread t0 = new Thread(run); Thread t1 = new Thread(run); Thread t2 = new Thread(run); //調用start方法開啓多線程 t0.start(); t1.start(); t2.start(); } }
結果:
Thread-1-->正在賣第100張票 Thread-0-->正在賣第99張票 Thread-2-->正在賣第98張票 Thread-1-->正在賣第97張票 Thread-0-->正在賣第96張票 Thread-2-->正在賣第95張票 Thread-1-->正在賣第94張票 Thread-0-->正在賣第94張票 Thread-2-->正在賣第92張票 Thread-1-->正在賣第91張票 Thread-0-->正在賣第91張票 Thread-2-->正在賣第89張票 Thread-0-->正在賣第88張票 Thread-1-->正在賣第88張票 Thread-2-->正在賣第86張票 Thread-0-->正在賣第85張票 Thread-1-->正在賣第85張票 Thread-2-->正在賣第83張票 Thread-1-->正在賣第82張票 Thread-0-->正在賣第82張票 Thread-2-->正在賣第80張票 Thread-0-->正在賣第79張票 Thread-1-->正在賣第79張票 Thread-2-->正在賣第77張票 Thread-1-->正在賣第76張票 Thread-0-->正在賣第76張票 Thread-2-->正在賣第74張票 Thread-1-->正在賣第73張票 Thread-0-->正在賣第73張票 Thread-2-->正在賣第71張票 Thread-1-->正在賣第70張票 Thread-0-->正在賣第70張票 Thread-2-->正在賣第68張票 Thread-0-->正在賣第67張票 Thread-1-->正在賣第67張票 Thread-2-->正在賣第65張票 Thread-0-->正在賣第64張票 Thread-1-->正在賣第64張票 Thread-2-->正在賣第62張票 Thread-1-->正在賣第61張票 Thread-0-->正在賣第61張票 Thread-2-->正在賣第59張票 Thread-0-->正在賣第58張票 Thread-1-->正在賣第58張票 Thread-2-->正在賣第56張票 Thread-1-->正在賣第55張票 Thread-0-->正在賣第55張票 Thread-2-->正在賣第53張票 Thread-1-->正在賣第52張票 Thread-0-->正在賣第52張票 Thread-2-->正在賣第50張票 Thread-0-->正在賣第49張票 Thread-1-->正在賣第49張票 Thread-2-->正在賣第47張票 Thread-0-->正在賣第46張票 Thread-1-->正在賣第46張票 Thread-2-->正在賣第44張票 Thread-1-->正在賣第43張票 Thread-0-->正在賣第42張票 Thread-2-->正在賣第41張票 Thread-2-->正在賣第40張票 Thread-1-->正在賣第40張票 Thread-0-->正在賣第40張票 Thread-1-->正在賣第37張票 Thread-2-->正在賣第37張票 Thread-0-->正在賣第37張票 Thread-0-->正在賣第34張票 Thread-1-->正在賣第34張票 Thread-2-->正在賣第34張票 Thread-1-->正在賣第31張票 Thread-2-->正在賣第31張票 Thread-0-->正在賣第31張票 Thread-1-->正在賣第28張票 Thread-0-->正在賣第28張票 Thread-2-->正在賣第28張票 Thread-2-->正在賣第25張票 Thread-1-->正在賣第25張票 Thread-0-->正在賣第25張票 Thread-1-->正在賣第22張票 Thread-0-->正在賣第22張票 Thread-2-->正在賣第22張票 Thread-1-->正在賣第19張票 Thread-0-->正在賣第19張票 Thread-2-->正在賣第19張票 Thread-0-->正在賣第16張票 Thread-1-->正在賣第16張票 Thread-2-->正在賣第16張票 Thread-1-->正在賣第13張票 Thread-0-->正在賣第13張票 Thread-2-->正在賣第13張票 Thread-0-->正在賣第10張票 Thread-1-->正在賣第10張票 Thread-2-->正在賣第10張票 Thread-0-->正在賣第7張票 Thread-1-->正在賣第7張票 Thread-2-->正在賣第7張票 Thread-2-->正在賣第4張票 Thread-1-->正在賣第4張票 Thread-0-->正在賣第4張票 Thread-0-->正在賣第1張票 Thread-1-->正在賣第0張票 Thread-2-->正在賣第-1張票
結果出現了賣重複的票,賣不存在的票。
二、線程安全問題的解決方案:
1)、同步代碼塊:
/* 格式: synchronized(鎖對象){ 可能會出現線程安全問題的代碼(訪問了共享數據的代碼) } 注意: 1.經過代碼塊中的鎖對象,可使用任意的對象 2.可是必須保證多個線程使用的鎖對象是同一個 3.鎖對象做用: 把同步代碼塊鎖住,只讓一個線程在同步代碼塊中執行 */
修改線程實現類:
/* 實現賣票案例 */ public class RunnableImpl implements Runnable{ //定義一個多個線程共享的票源 private int ticket = 100; //建立一個鎖對象 Object object=new Object(); //設置線程任務:賣票 @Override public void run() { //使用死循環,讓賣票操做重複執行 while(true){ //同步代碼塊 synchronized (object){ //先判斷票是否存在 if(ticket>0){ try { Thread.sleep(10); //提升安全問題出現的機率,讓程序睡眠 } catch (InterruptedException e) { e.printStackTrace(); } //票存在,賣票 ticket-- System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票"); ticket--; } } } } }
將可能出現線程安全問題的代碼放到 synchronized (object){} 代碼塊中。
結果:
Thread-0-->正在賣第100張票 Thread-0-->正在賣第99張票 Thread-0-->正在賣第98張票 Thread-0-->正在賣第97張票 Thread-2-->正在賣第96張票 Thread-2-->正在賣第95張票 Thread-2-->正在賣第94張票 Thread-2-->正在賣第93張票 Thread-2-->正在賣第92張票 Thread-2-->正在賣第91張票 Thread-2-->正在賣第90張票 Thread-2-->正在賣第89張票 Thread-2-->正在賣第88張票 Thread-2-->正在賣第87張票 Thread-2-->正在賣第86張票 Thread-2-->正在賣第85張票 Thread-2-->正在賣第84張票 Thread-2-->正在賣第83張票 Thread-2-->正在賣第82張票 Thread-2-->正在賣第81張票 Thread-2-->正在賣第80張票 Thread-2-->正在賣第79張票 Thread-2-->正在賣第78張票 Thread-2-->正在賣第77張票 Thread-2-->正在賣第76張票 Thread-2-->正在賣第75張票 Thread-2-->正在賣第74張票 Thread-2-->正在賣第73張票 Thread-2-->正在賣第72張票 Thread-2-->正在賣第71張票 Thread-2-->正在賣第70張票 Thread-2-->正在賣第69張票 Thread-2-->正在賣第68張票 Thread-2-->正在賣第67張票 Thread-2-->正在賣第66張票 Thread-2-->正在賣第65張票 Thread-2-->正在賣第64張票 Thread-2-->正在賣第63張票 Thread-2-->正在賣第62張票 Thread-2-->正在賣第61張票 Thread-2-->正在賣第60張票 Thread-2-->正在賣第59張票 Thread-2-->正在賣第58張票 Thread-2-->正在賣第57張票 Thread-2-->正在賣第56張票 Thread-2-->正在賣第55張票 Thread-2-->正在賣第54張票 Thread-2-->正在賣第53張票 Thread-2-->正在賣第52張票 Thread-2-->正在賣第51張票 Thread-2-->正在賣第50張票 Thread-2-->正在賣第49張票 Thread-2-->正在賣第48張票 Thread-2-->正在賣第47張票 Thread-2-->正在賣第46張票 Thread-2-->正在賣第45張票 Thread-2-->正在賣第44張票 Thread-2-->正在賣第43張票 Thread-2-->正在賣第42張票 Thread-2-->正在賣第41張票 Thread-2-->正在賣第40張票 Thread-2-->正在賣第39張票 Thread-2-->正在賣第38張票 Thread-2-->正在賣第37張票 Thread-2-->正在賣第36張票 Thread-2-->正在賣第35張票 Thread-2-->正在賣第34張票 Thread-2-->正在賣第33張票 Thread-2-->正在賣第32張票 Thread-2-->正在賣第31張票 Thread-2-->正在賣第30張票 Thread-2-->正在賣第29張票 Thread-2-->正在賣第28張票 Thread-2-->正在賣第27張票 Thread-2-->正在賣第26張票 Thread-2-->正在賣第25張票 Thread-2-->正在賣第24張票 Thread-2-->正在賣第23張票 Thread-2-->正在賣第22張票 Thread-2-->正在賣第21張票 Thread-2-->正在賣第20張票 Thread-2-->正在賣第19張票 Thread-2-->正在賣第18張票 Thread-2-->正在賣第17張票 Thread-2-->正在賣第16張票 Thread-2-->正在賣第15張票 Thread-2-->正在賣第14張票 Thread-2-->正在賣第13張票 Thread-2-->正在賣第12張票 Thread-2-->正在賣第11張票 Thread-2-->正在賣第10張票 Thread-2-->正在賣第9張票 Thread-1-->正在賣第8張票 Thread-1-->正在賣第7張票 Thread-1-->正在賣第6張票 Thread-1-->正在賣第5張票 Thread-1-->正在賣第4張票 Thread-1-->正在賣第3張票 Thread-1-->正在賣第2張票 Thread-1-->正在賣第1張票
再也不出現重複票,和不存在的票了。
實現原理:使用一個鎖對象,這個對象叫作同步鎖,也叫做對象監視器。
上述代碼中三個線程搶佔CPU執行權,
t0 搶到了CPUd 執行權,執行run方法,遇到synchronized代碼塊,
這時候 t0 會檢查 synchronized 代碼塊是否有鎖對象,
發現有就會獲取到鎖對象進入到同步代碼塊執行。
t1 搶到了CPU的執行權,執行 run 方法,遇到 synchronized 代碼塊
這時 t1會檢查synchronized代碼塊是否有鎖對象
發現沒有 t1 就會進入到阻塞狀態,會一直等到 t0 線程歸還 鎖對象
一直到 t0 線程執行完同步代碼,會把鎖對象歸還給同步代碼塊。
t1 才能獲取到 鎖對象 進入到同步執行。
總結:同步中的線程沒有執行完畢不會釋放鎖,沒有鎖也進不進同步代碼,
這樣就保證了只有一個線程在同步中執行共享數據,保證了安全,
程序頻繁的判斷鎖,獲取鎖,釋放鎖,效率會下降。
2)、同步方法
修改線程實現類:
/* 實現賣票案例 */ public class RunnableImpl implements Runnable{ //定義一個多個線程共享的票源 private int ticket = 100; //建立一個鎖對象 Object object=new Object(); //設置線程任務:賣票 @Override public void run() { //使用死循環,讓賣票操做重複執行 while(true){ payTicket(); } } /* 定義一個同步方法 同步方法也會把方法內部的代碼鎖住 只讓一個線程執行 同步方法的對象是誰? 就是實現類對象 new RunnableImpl() */ public synchronized void payTicket(){ //先判斷票是否存在 if(ticket>0){ try { Thread.sleep(10); //提升安全問題出現的機率,讓程序睡眠 } catch (InterruptedException e) { e.printStackTrace(); } //票存在,賣票 ticket-- System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票"); ticket--; } } }
還能夠用靜態的同步代碼:
package DemoThread; /* 實現賣票案例 */ public class RunnableImpl implements Runnable{ //定義一個多個線程共享的票源 private static int ticket = 100; //建立一個鎖對象 Object object=new Object(); //設置線程任務:賣票 @Override public void run() { //使用死循環,讓賣票操做重複執行 while(true){ payTicketStatic(); } } /* 定義一個靜態的同步方法 對象是誰? 就是實現類對象 本類的class屬相 --> class文件對象(反射) */ public static synchronized void payTicketStatic(){ //先判斷票是否存在 if(ticket>0){ try { Thread.sleep(10); //提升安全問題出現的機率,讓程序睡眠 } catch (InterruptedException e) { e.printStackTrace(); } //票存在,賣票 ticket-- System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票"); ticket--; } } }
將可能出現線程安全問題的代碼放到 synchronized 修飾的方法中。
結果:
Thread-0-->正在賣第100張票 Thread-2-->正在賣第99張票 Thread-2-->正在賣第98張票 Thread-2-->正在賣第97張票 Thread-2-->正在賣第96張票 Thread-2-->正在賣第95張票 Thread-2-->正在賣第94張票 Thread-2-->正在賣第93張票 Thread-2-->正在賣第92張票 Thread-2-->正在賣第91張票 Thread-2-->正在賣第90張票 Thread-2-->正在賣第89張票 Thread-2-->正在賣第88張票 Thread-2-->正在賣第87張票 Thread-2-->正在賣第86張票 Thread-2-->正在賣第85張票 Thread-2-->正在賣第84張票 Thread-2-->正在賣第83張票 Thread-2-->正在賣第82張票 Thread-2-->正在賣第81張票 Thread-2-->正在賣第80張票 Thread-2-->正在賣第79張票 Thread-2-->正在賣第78張票 Thread-2-->正在賣第77張票 Thread-2-->正在賣第76張票 Thread-2-->正在賣第75張票 Thread-2-->正在賣第74張票 Thread-2-->正在賣第73張票 Thread-2-->正在賣第72張票 Thread-2-->正在賣第71張票 Thread-2-->正在賣第70張票 Thread-2-->正在賣第69張票 Thread-2-->正在賣第68張票 Thread-2-->正在賣第67張票 Thread-2-->正在賣第66張票 Thread-2-->正在賣第65張票 Thread-2-->正在賣第64張票 Thread-2-->正在賣第63張票 Thread-2-->正在賣第62張票 Thread-2-->正在賣第61張票 Thread-2-->正在賣第60張票 Thread-2-->正在賣第59張票 Thread-2-->正在賣第58張票 Thread-2-->正在賣第57張票 Thread-2-->正在賣第56張票 Thread-2-->正在賣第55張票 Thread-2-->正在賣第54張票 Thread-2-->正在賣第53張票 Thread-2-->正在賣第52張票 Thread-2-->正在賣第51張票 Thread-2-->正在賣第50張票 Thread-2-->正在賣第49張票 Thread-2-->正在賣第48張票 Thread-2-->正在賣第47張票 Thread-2-->正在賣第46張票 Thread-2-->正在賣第45張票 Thread-2-->正在賣第44張票 Thread-2-->正在賣第43張票 Thread-2-->正在賣第42張票 Thread-2-->正在賣第41張票 Thread-2-->正在賣第40張票 Thread-2-->正在賣第39張票 Thread-2-->正在賣第38張票 Thread-2-->正在賣第37張票 Thread-2-->正在賣第36張票 Thread-2-->正在賣第35張票 Thread-2-->正在賣第34張票 Thread-2-->正在賣第33張票 Thread-2-->正在賣第32張票 Thread-2-->正在賣第31張票 Thread-2-->正在賣第30張票 Thread-2-->正在賣第29張票 Thread-2-->正在賣第28張票 Thread-2-->正在賣第27張票 Thread-2-->正在賣第26張票 Thread-2-->正在賣第25張票 Thread-2-->正在賣第24張票 Thread-2-->正在賣第23張票 Thread-2-->正在賣第22張票 Thread-2-->正在賣第21張票 Thread-2-->正在賣第20張票 Thread-2-->正在賣第19張票 Thread-2-->正在賣第18張票 Thread-2-->正在賣第17張票 Thread-2-->正在賣第16張票 Thread-2-->正在賣第15張票 Thread-2-->正在賣第14張票 Thread-2-->正在賣第13張票 Thread-2-->正在賣第12張票 Thread-2-->正在賣第11張票 Thread-2-->正在賣第10張票 Thread-2-->正在賣第9張票 Thread-2-->正在賣第8張票 Thread-2-->正在賣第7張票 Thread-2-->正在賣第6張票 Thread-2-->正在賣第5張票 Thread-2-->正在賣第4張票 Thread-2-->正在賣第3張票 Thread-2-->正在賣第2張票 Thread-2-->正在賣第1張票
同步方法和靜態同步的方法的不一樣點在於,同步鎖的對象不一樣。
同步方法 的鎖對象就是 實現類對象 new RunnableImpl()
靜態同步的方法 的鎖對象就是 本類的 RunnableImpl.class。
3)、鎖機制
代碼實現:
/* 賣票案例出現了線程安全問題 賣出了不存在的票和重複的票 解決線程安全問題的三種方案:使用Lock鎖 java.util.concurrent.locks.Lock接口 Lock 實現提供了比使用 synchronized 方法和語句可得到的更普遍的鎖定操做。 Lock接口中的方法: void lock()獲取鎖。 void unlock() 釋放鎖。 java.util.concurrent.locks.ReentrantLock implements Lock接口 使用步驟: 1.在成員位置建立一個ReentrantLock對象 2.在可能會出現安全問題的代碼前調用Lock接口中的方法lock獲取鎖 3.在可能會出現安全問題的代碼後調用Lock接口中的方法unlock釋放鎖 */ public class RunnableImpl implements Runnable{ //定義一個多個線程共享的票源 private int ticket = 100; //1.在成員位置建立一個ReentrantLock對象 Lock lock1 = new ReentrantLock(); //設置線程任務:賣票 @Override public void run() { //使用死循環,讓賣票操做重複執行 while(true){ lock1.lock(); //2.在可能會出現安全問題的代碼前調用Lock接口中的方法lock獲取鎖 if(ticket>0){ try { Thread.sleep(10); //提升安全問題出現的機率,讓程序睡眠 //票存在,賣票 ticket-- System.out.println(Thread.currentThread().getName()+"-->正在賣第"+ticket+"張票"); ticket--; } catch (InterruptedException e) { e.printStackTrace(); }finally { lock1.unlock();//3.在可能會出現安全問題的代碼後調用Lock接口中的方法unlock釋放鎖 //放在finally{} 中 不管程序是否異常,都會把鎖釋放。 } } } } }
結果同上面的兩種方式相同。
線程狀態 | 致使狀態發生條件 |
NEW(新建) | 線程剛被建立,可是並未啓動。還沒調用start方法。 |
Runnable(可運行)
|
線程能夠在java虛擬機中運行的狀態,可能正在運行本身代碼,也可能沒有,這取決於操
做系統處理器。
|
Blocked(鎖阻塞)
|
當一個線程試圖獲取一個對象鎖,而該對象鎖被其餘的線程持有,則該線程進入Blocked狀
態;當該線程持有鎖時,該線程將變成Runnable狀態。
|
Waiting(無限等待)
|
一個線程在等待另外一個線程執行一個(喚醒)動做時,該線程進入Waiting狀態。進入這個
狀態後是不能自動喚醒的,必須等待另外一個線程調用notify或者notifyAll方法纔可以喚醒。
|
TimedWaiting(計時等待)
|
同waiting狀態,有幾個方法有超時參數,調用他們將進入Timed Waiting狀態。這一狀態
將一直保持到超時期滿或者接收到喚醒通知。帶有超時參數的經常使用方法有Thread.sleep 、Object.wait。
|
Teminated(被終止)
|
由於run方法正常退出而死亡,或者由於沒有捕獲的異常終止了run方法而死亡。 |
上面已經講過了同步機制,那麼這個狀態也就很是好理解了,好比線程A與線程B代碼中使用
同一把鎖,若是線程A獲取到鎖,線程A進入到Runnable狀態,那麼線程B進入到Blocked鎖阻塞狀態。
這裏是由Runnable狀態進入Blocked狀態,除此以外Waiting(無限等待)以及
Time Waiting(計時等待)也會在某種狀況下進入到阻塞狀態
/* 等待喚醒案例:線程之間的通訊 建立一個顧客線程(消費者):告知老闆要的包子的種類和數量,調用wait方法,放棄cpu的執行,進入到WAITING狀態(無限等待) 建立一個老闆線程(生產者):花了5秒作包子,作好包子以後,調用notify方法,喚醒顧客吃包子 注意: 顧客和老闆線程必須使用同步代碼塊包裹起來,保證等待和喚醒只能有一個在執行 同步使用的鎖對象必須保證惟一 只有鎖對象才能調用wait和notify方法 Obejct類中的方法 void wait() 在其餘線程調用此對象的 notify() 方法或 notifyAll() 方法前,致使當前線程等待。 void notify() 喚醒在此對象監視器上等待的單個線程。 會繼續執行wait方法以後的代碼 */ public class MainThread { public static void main(String[] args) { //建立鎖對象,保證惟一 Object obj = new Object(); // 建立一個顧客線程(消費者) new Thread(){ @Override public void run() { //一直等着買包子 while(true){ //保證等待和喚醒的線程只能有一個執行,須要使用同步技術 synchronized (obj){ System.out.println("告知老闆要的包子的種類和數量"); //調用wait方法,放棄cpu的執行,進入到WAITING狀態(無限等待) try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //喚醒以後執行的代碼 System.out.println("包子已經作好了,開吃!"); System.out.println("---------------------------------------"); } } } }.start(); //建立一個老闆線程(生產者) new Thread(){ @Override public void run() { //一直作包子 while (true){ //花了5秒作包子 try { Thread.sleep(5000);//花5秒鐘作包子 } catch (InterruptedException e) { e.printStackTrace(); } //保證等待和喚醒的線程只能有一個執行,須要使用同步技術 synchronized (obj){ System.out.println("老闆5秒鐘以後作好包子,告知顧客,能夠吃包子了"); //作好包子以後,調用notify方法,喚醒顧客吃包子 obj.notify(); } } } }.start(); } }
結果:
告知老闆要的包子的種類和數量 老闆5秒鐘以後作好包子,告知顧客,能夠吃包子了 包子已經作好了,開吃! --------------------------------------- 告知老闆要的包子的種類和數量 老闆5秒鐘以後作好包子,告知顧客,能夠吃包子了 包子已經作好了,開吃! --------------------------------------- 告知老闆要的包子的種類和數量
代碼實現:
/* 進入到TimeWaiting(計時等待)有兩種方式 1.使用sleep(long m)方法,在毫秒值結束以後,線程睡醒進入到Runnable/Blocked狀態 2.使用wait(long m)方法,wait方法若是在毫秒值結束以後,尚未被notify喚醒,就會自動醒來,線程睡醒進入到Runnable/Blocked狀態 喚醒的方法: void notify() 喚醒在此對象監視器上等待的單個線程。 void notifyAll() 喚醒在此對象監視器上等待的全部線程。 */ public class MainThread { public static void main(String[] args) { //建立鎖對象,保證惟一 Object obj = new Object(); // 建立一個顧客線程(消費者) new Thread(){ @Override public void run() { //一直等着買包子 while(true){ //保證等待和喚醒的線程只能有一個執行,須要使用同步技術 synchronized (obj){ System.out.println("顧客1告知老闆要的包子的種類和數量"); //調用wait方法,放棄cpu的執行,進入到WAITING狀態(無限等待) try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //喚醒以後執行的代碼 System.out.println("包子已經作好了,顧客1開吃!"); System.out.println("---------------------------------------"); } } } }.start(); // 建立一個顧客線程(消費者) new Thread(){ @Override public void run() { //一直等着買包子 while(true){ //保證等待和喚醒的線程只能有一個執行,須要使用同步技術 synchronized (obj){ System.out.println("顧客2告知老闆要的包子的種類和數量"); //調用wait方法,放棄cpu的執行,進入到WAITING狀態(無限等待) try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } //喚醒以後執行的代碼 System.out.println("包子已經作好了,顧客2開吃!"); System.out.println("---------------------------------------"); } } } }.start(); //建立一個老闆線程(生產者) new Thread(){ @Override public void run() { //一直作包子 while (true){ //花了5秒作包子 try { Thread.sleep(5000);//花5秒鐘作包子 } catch (InterruptedException e) { e.printStackTrace(); } //保證等待和喚醒的線程只能有一個執行,須要使用同步技術 synchronized (obj){ System.out.println("老闆5秒鐘以後作好包子,告知顧客,能夠吃包子了"); //作好包子以後,調用notify方法,喚醒顧客吃包子 //obj.notify();//若是有多個等待線程,隨機喚醒一個 obj.notifyAll();//喚醒全部等待的線程 } } } }.start(); } }
結果:
顧客1告知老闆要的包子的種類和數量 顧客2告知老闆要的包子的種類和數量 老闆5秒鐘以後作好包子,告知顧客,能夠吃包子了 包子已經作好了,顧客2開吃! --------------------------------------- 顧客2告知老闆要的包子的種類和數量 包子已經作好了,顧客1開吃! --------------------------------------- 顧客1告知老闆要的包子的種類和數量 老闆5秒鐘以後作好包子,告知顧客,能夠吃包子了 包子已經作好了,顧客1開吃! --------------------------------------- 顧客1告知老闆要的包子的種類和數量 包子已經作好了,顧客2開吃! ---------------------------------------
以下所示:
等待喚醒機制其實就是經典的「生產者與消費者」的問題。
從新整理代碼:
BaoZi類:
/* 資源類:包子類 設置包子的屬性 皮 陷 包子的狀態: 有 true,沒有 false */ public class BaoZi { private String pier ; private String xianer ; private boolean flag = false ;//包子資源 是否存在 包子資源狀態 public String getPier() { return pier; } public void setPier(String pier) { this.pier = pier; } public String getXianer() { return xianer; } public void setXianer(String xianer) { this.xianer = xianer; } public boolean isFlag() { return flag; } public void setFlag(boolean flag) { this.flag = flag; } }
BaoZiPu類:
/* 生產者(包子鋪)類:是一個線程類,能夠繼承Thread 設置線程任務(run):生產包子 對包子的狀態進行判斷 true:有包子 包子鋪調用wait方法進入等待狀態 false:沒有包子 包子鋪生產包子 增長一些趣味性:交替生產兩種包子 有兩種狀態(i%2==0) 包子鋪生產好了包子 修改包子的狀態爲true有 喚醒吃貨線程,讓吃貨線程吃包子 注意: 包子鋪線程和包子線程關係-->通訊(互斥) 必須同時同步技術保證兩個線程只能有一個在執行 鎖對象必須保證惟一,可使用包子對象做爲鎖對象 包子鋪類和吃貨的類就須要把包子對象做爲參數傳遞進來 1.須要在成員位置建立一個包子變量 2.使用帶參數構造方法,爲這個包子變量賦值 */ public class BaoZiPu extends Thread { //1.須要在成員位置建立一個包子變量 private BaoZi bz; //2.使用帶參數構造方法,爲這個包子變量賦值 public BaoZiPu(BaoZi bz) { this.bz = bz; } //設置線程任務(run):生產包子 @Override public void run() { //定義一個變量 int count = 0; //讓包子鋪一直生產包子 while(true){ //必須同時同步技術保證兩個線程只能有一個在執行 synchronized (bz){ //對包子的狀態進行判斷 if(bz.isFlag()==true){ //包子鋪調用wait方法進入等待狀態 try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //被喚醒以後執行,包子鋪生產包子 //增長一些趣味性:交替生產兩種包子 if(count%2==0){ //生產 薄皮三鮮餡包子 bz.setPier("薄皮"); bz.setXianer("三鮮餡"); }else{ //生產 冰皮 牛肉大蔥陷 bz.setPier("冰皮"); bz.setXianer("牛肉大蔥陷"); } count++; System.out.println("包子鋪正在生產:"+bz.getPier()+bz.getXianer()+"包子"); //生產包子須要3秒鐘 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } //包子鋪生產好了包子 //修改包子的狀態爲true有 bz.setFlag(true); //喚醒吃貨線程,讓吃貨線程吃包子 bz.notify(); System.out.println("包子鋪已經生產好了:"+bz.getPier()+bz.getXianer()+"包子,吃貨能夠開始吃了"); } } } }
ChiHuo類:
/* 消費者(吃貨)類:是一個線程類,能夠繼承Thread 設置線程任務(run):吃包子 對包子的狀態進行判斷 false:沒有包子 吃貨調用wait方法進入等待狀態 true:有包子 吃貨吃包子 吃貨吃完包子 修改包子的狀態爲false沒有 吃貨喚醒包子鋪線程,生產包子 */ public class ChiHuo extends Thread{ //1.須要在成員位置建立一個包子變量 private BaoZi bz; //2.使用帶參數構造方法,爲這個包子變量賦值 public ChiHuo(BaoZi bz) { this.bz = bz; } //設置線程任務(run):吃包子 @Override public void run() { //使用死循環,讓吃貨一直吃包子 while (true){ //必須同時同步技術保證兩個線程只能有一個在執行 synchronized (bz){ //對包子的狀態進行判斷 if(bz.isFlag()==false){ //吃貨調用wait方法進入等待狀態 try { bz.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //被喚醒以後執行的代碼,吃包子 System.out.println("吃貨正在吃:"+bz.getPier()+bz.getXianer()+"的包子"); //吃貨吃完包子 //修改包子的狀態爲false沒有 bz.setFlag(false); //吃貨喚醒包子鋪線程,生產包子 bz.notify(); System.out.println("吃貨已經把:"+bz.getPier()+bz.getXianer()+"的包子吃完了,包子鋪開始生產包子"); System.out.println("----------------------------------------------------"); } } } }
測試類:
/* 測試類: 包含main方法,程序執行的入口,啓動程序 建立包子對象; 建立包子鋪線程,開啓,生產包子; 建立吃貨線程,開啓,吃包子; */ public class Demo { public static void main(String[] args) { //建立包子對象; BaoZi bz =new BaoZi(); //建立包子鋪線程,開啓,生產包子; new BaoZiPu(bz).start(); //建立吃貨線程,開啓,吃包子; new ChiHuo(bz).start(); } }
結果:
包子鋪正在生產:薄皮三鮮餡包子 包子鋪已經生產好了:薄皮三鮮餡包子,吃貨能夠開始吃了 吃貨正在吃:薄皮三鮮餡的包子 吃貨已經把:薄皮三鮮餡的包子吃完了,包子鋪開始生產包子 ---------------------------------------------------- 包子鋪正在生產:冰皮牛肉大蔥陷包子 包子鋪已經生產好了:冰皮牛肉大蔥陷包子,吃貨能夠開始吃了 吃貨正在吃:冰皮牛肉大蔥陷的包子 吃貨已經把:冰皮牛肉大蔥陷的包子吃完了,包子鋪開始生產包子 ---------------------------------------------------- 包子鋪正在生產:薄皮三鮮餡包子 包子鋪已經生產好了:薄皮三鮮餡包子,吃貨能夠開始吃了 吃貨正在吃:薄皮三鮮餡的包子 吃貨已經把:薄皮三鮮餡的包子吃完了,包子鋪開始生產包子 ---------------------------------------------------- 包子鋪正在生產:冰皮牛肉大蔥陷包子
原理說明:
通信:對包子的狀態進行判斷
沒有包子-->吃貨線程 喚醒包子鋪線程-->吃貨等待-->包子鋪線程作包子-->作好包子-->修改包子的狀態爲有包子
有包子-->包子鋪線程喚醒 吃貨線程-->包子鋪線程等待-->吃貨線程吃包子-->吃完包子-->修改包子的狀態爲沒有包子
。。。
爲何使用線程:
當併發的線程數量不少,而且每一個線程都是執行一個時間很短的任務就結束了,
這樣頻繁建立線程就會大大的下降系統的效率,由於頻繁建立線程和銷燬線程須要時間。
線程池:其實就是一個容納多個線程的容器,其中線程能夠反覆使用,省去了頻繁建立線程對象的操做,
無需反覆建立線程池而消耗了過多的資源。
圖解:
合理利用線程池可以帶來三個好處:
一、下降資源消耗。減小了建立和銷燬線程的次數,每一個工做線程均可以被重複使用,可執行多個任務。
二、提升響應速度。當任務到達時,任務能夠不須要的等到線程建立就能當即執行
三、提升線程的可管理性。能夠根據系統的承受能力,調整線程池中工做線線程的數目,防止由於消耗過多的內
/* 2.建立一個類,實現Runnable接口,重寫run方法,設置線程任務 */ public class RunnableImpl implements Runnable{ @Override public void run() { System.out.println(Thread.currentThread().getName()+"建立了一個新的線程執行"); } }
測試類;
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /* 線程池:JDK1.5以後提供的 java.util.concurrent.Executors:線程池的工廠類,用來生成線程池 Executors類中的靜態方法: static ExecutorService newFixedThreadPool(int nThreads) 建立一個可重用固定線程數的線程池 參數: int nThreads:建立線程池中包含的線程數量 返回值: ExecutorService接口,返回的是ExecutorService接口的實現類對象,
咱們可使用ExecutorService接口接收(面向接口編程) java.util.concurrent.ExecutorService:線程池接口 用來從線程池中獲取線程,調用start方法,執行線程任務 submit(Runnable task) 提交一個 Runnable 任務用於執行 關閉/銷燬線程池的方法 void shutdown() 線程池的使用步驟: 1.使用線程池的工廠類Executors裏邊提供的靜態方法newFixedThreadPool生產一個指定線程數量的線程池 2.建立一個類,實現Runnable接口,重寫run方法,設置線程任務 3.調用ExecutorService中的方法submit,傳遞線程任務(實現類),開啓線程,執行run方法 4.調用ExecutorService中的方法shutdown銷燬線程池(不建議執行) */ public class MainThread { public static void main(String[] args) { //1.使用線程池的工廠類Executors裏邊提供的靜態方法newFixedThreadPool生產一個指定線程數量的線程池 ExecutorService es = Executors.newFixedThreadPool(2); //3.調用ExecutorService中的方法submit,傳遞線程任務(實現類),開啓線程,執行run方法 es.submit(new RunnableImpl());//pool-1-thread-1建立了一個新的線程執行 //線程池會一直開啓,使用完了線程,會自動把線程歸還給線程池,線程能夠繼續使用 es.submit(new RunnableImpl());//pool-1-thread-1建立了一個新的線程執行 es.submit(new RunnableImpl());//pool-1-thread-2建立了一個新的線程執行 //4.調用ExecutorService中的方法shutdown銷燬線程池(不建議執行) es.shutdown(); es.submit(new RunnableImpl());//拋異常,線程池都沒有了,就不能獲取線程了 } }
結果:
pool-1-thread-1建立了一個新的線程執行 pool-1-thread-2建立了一個新的線程執行 pool-1-thread-1建立了一個新的線程執行 pool-1-thread-2建立了一個新的線程執行 pool-1-thread-2建立了一個新的線程執行
由於線程使用完了,會自動把線程歸還給線程池,線程能夠繼續使用,因此我只在線程池中設置了兩個線程,
卻能夠反覆使用,執行屢次任務。
待續。。。。