一、多線程引入
1、我們在之前寫的代碼程序只有一個執行流程,這樣的程序就是單線程程序。2、假如一個程序有多條執行流程,那麼,該程序就是多線程程序。
二、進程和線程的概念:
1、進程:應用程序在內存中運行的空間
2、線程:是進程中的一個執行單元,負責執行進程中的代碼。
3、一個進程如果只有一條執行路徑,則稱爲單線程程序。
4、一個進程如果有多條執行路徑,則稱爲多線程程序。
三、多線程的存在解決什麼問題?
1、多部分代碼同時執行的問題。
2、傳統的單個主線程從頭執行到尾的運行安排,當遇到較多的操作次數時,效率偏低。
四、多線程的弊端:
1、開啓過多會降低效率,多線程的原理其實是多個執行單元執行一個程序,CPU高速在多個線程之間進行切換,當線程個數過多時,CPU切換一個循環的時間過長,相應的降低了程序運行的效率。
五、多線程的特性:
1、隨機性,因爲CPU的快速切換造成的。
六、java程序運行原理
1、java 命令會啓動 java 虛擬機,啓動 JVM,等於啓動了一個應用程序,也就是啓動了一個進程。該進程會自動啓動一個 “主線程” ,然後主線程去調用某個類的 main 方法。
所以 main方法運行在主線程中。在此之前的所有程序都是單線程的。
2、思考:jvm虛擬機的啓動是單線程的還是多線程的?
jvm的啓動是多線程的,因爲它最低有兩個線程啓動了,主線程和垃圾回收線程。
七、舉例:
1、金山衛士進行電腦體檢的同時,還可以清理垃圾,木馬查殺等,這就是多線程。
第二節:創建線程的兩種方式
一、方式一:
1、創建方式:繼承Thread類。
(3).1 定義一個類繼承Thread。原因:Thread類描述了線程的任務存在的位置:run方法。
(3).2 重寫run方法。原因:爲了定義線程需要運行的任務代碼。
(3).3 創建子類對象,就是創建線程對象。目的:爲了執行線程任務,而是任務都定義在run方法中。run方法結束了,線程任務就是結束了,線程也就是結束了
(線程任務:每一個線程都有自己執行的代碼,主線程的代碼都定義在主函數中,自定義線程的代碼都定義在run方法中。)
(3).4 調用start方法,原因:開啓線程並讓線程執行,同時還會告訴jvm去調用run方法。
2、調用run和調用start的區別?
調用run方法僅僅是主線程執行run方法中的代碼;調用start方法是開啓線程,讓新建的線程與主線程同時執行。
3、多線程的運行原理
(3).1 如果多部分代碼同時執行,那麼在棧內存方法執行是怎麼完成的呢?
其實是,每一個線程在棧內存中都有自己的棧內存空間,在自己的棧空間中
進行壓棧和彈棧。
(3).2 主線程結束,程序就結束嗎?
主線程結束,如果其他線程還在執行,進程是不會結束了,當所有的線程都結束了,進程纔會結束。
3、代碼體現
- //創建線程的第一種方式 繼承Thread
- //開啓兩個窗口進行賣票 第一種方式
- /*
- 思路:
- 1 創建一個類 繼承thread類並覆蓋run方法
- 2 創建子類對象
- 3 調用start方法
- */
- class Ticket extends Thread
- {//自定義變量並給一定的值
- private int ticket=100;
- //覆蓋run方法
- public void run()
- {
- //進行循環
- while (true)
- {
- //判斷票的數量
- if (ticket>0)
- {
- //打印輸出
- System.out.println(Thread.currentThread().getName()+".............................."+ticket--);
- }
- else
- {
- break;
- }
- }
- }
- }
- class Demo
- {
- public static void main(String[] args)
- {
- //創建Thread子類的對象
- Ticket t=new Ticket();
- Ticket t1=new Ticket();
- //並調用start的方法
- t.start();
- t1.start();
- }
- }
二、方式二:
1、方式二步驟:
(1).1,定義類實現Runnable接口。原因:避免了繼承Thread類的單繼承侷限性。
(1).2,覆蓋接口中的run方法。原因:將線程任務代碼定義到run方法中。
(1).3,創建Thread類的對象。原因:只有創建Thread類的對象纔可以創建線程。
(1).4,將Runnable接口的子類對象作爲參數傳遞給Thread類的構造函數。
原因:線程已被封裝到Runnable接口的run方法中,而這個run方法所屬於Runnable接口的子類對象,
所以將這個子類對象作爲參數傳遞給Thread的構造函數,這樣,線程對象創建時就可以明確要運行的線程的任務。
(1).5,調用Thread類的start方法開啓線程。
2、方式二和方式一的區別:
(2).1 第二種方式實現Runnable接口避免了單繼承的侷限性,所以較爲常用。
(2).2 實現Runnable接口的方式,更加的符合面向對象,線程分爲兩部分,一部分線程對象,一部分線程任務。
繼承Thread類:線程對象和線程任務耦合在一起。一旦創建Thread類的子類對象,既是線程對象,有又有線程任務。
實現runnable接口:將線程任務單獨分離出來封裝成對象,類型就是Runnable接口類型。
Runnable接口對線程對象和線程任務進行解耦。
3、代碼體現
- //創建兩個線程去賣票,用種第二方式
- /*
- 思路:
- 1 創建 一個類 去實現 Runable接口 並覆蓋run方法
- 2 創建一個類的對象 並把這個對象作爲參數傳遞給Thread類的構造函數中
- 3 調用start方法
- */
- //實現Runnable接口
- class Ticket implements Runnable
- {
- //定義一定量的票數
- private int ticket=200;
- //覆蓋run方法
- public void run()
- {
- //進行循環
- while (true)
- {
- //判斷票的數量
- if (ticket>0)
- {
- //進行打印
- System.out.println(Thread.currentThread().getName()+".............."+ticket--);
- }
- else
- {
- break;
- }
- }
- }
- }
- class Demo1
- {
- public static void main(String[] args)
- {
- //把Ticket()的類的對象傳入到Thread類對象的構造方法當中去,並調用start方法。
- new Thread(new Ticket()).start();
- new Thread(new Ticket()).start();
- }
- }
第三節:線程的運行的狀態
一、線程運行是有多種狀態的:
1、創建 new Thread類或者其子類對象。
2、運行 start()具備者CPU的執行資格和CPU的執行權。
3、凍結 釋放了CPU的執行資格和CPU的執行權。比如執行到sleep(time) wait() 導致線程凍結。
4、臨時阻塞狀態 具備者CPU的執行資格,不具備CPU的執行權。
5、消亡 線程結束。run方法結束。
第四節:線程的安全問題
一、多線程的安全問題:
1、安全問題產生的原因:
(1).1線程任務中有共享的數據。
(1).2線程任務中有多條操作共享數據的代碼。
當線程在操作共享數據時,其他線程參與了運算導致了數據的錯誤(安全問題)
2、安全問題的解決思路:
保證在線程執行共享數據代碼時,其他線程無法參與運算。
3、安全問題的解決代碼體現:
同步代碼塊。synchronized(obj){需要被同步的代碼}
4、同步的好處:
同步的出現解決了多線程的安全問題。
5、同步的弊端:
當線程相當多時,因爲每個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會降低程序的運行效率。
6、同步的前提:
必須保證多個線程在同步中使用的是同一個鎖。
解決了什麼問題?
當多線程安全問題發生時,加入了同步後,
問題依舊,就要通過這個同步的前提來判斷同步是否寫正確。
7、代碼體現
- //出現了線程的安全問題
- //解決安全方式之一同步函數 賣票問題
- //定義一個類實現Runnable接口
- class Ticket implements Runnable
- {
- //定義一個私有的變量票的數量
- private int ticket=10;
- //覆蓋run方法
- public void run()
- {
- //進行循環
- while (true)
- { //加上一個鎖
- synchronized(this)
- {
- //判斷票的數量大於零
- if (ticket>0)
- {
- //讓線程進行睡眠100毫秒,並進行異常的處理
- try{Thread.sleep(100);}catch(InterruptedException ie){}
- //進行打印
- System.out.println(Thread.currentThread().getName()+"................"+ticket--);
- }
- else
- {
- break;
- }
- }
- }
- }
- }
- class Demo2
- {
- public static void main(String[] args)
- {
- //創建Ticket類的對象
- Ticket t=new Ticket();
- //並把對象傳入到Thread類對象的構造函數當中去
- Thread tt=new Thread(t);
- Thread ttt=new Thread(t);
- //調用start的方法。
- tt.start();
- ttt.start();
- }
- }
二、同步函數
1、同步函數的表現形式。就是讓一個封裝體具備了同步性。
其實就是在函數上加上同步關鍵字。
2、同步函數的鎖用的哪個呢?
(2).1 非靜態的同步函數使用的鎖是: this
(2).2 靜態的同步函數使用的鎖時:類名.class
3、同步函數和同步代碼塊的區別。
(3).1同步函數使用的鎖是固定的。
(3).2同步代碼塊使用的鎖是任意對象。
(3).3如果線程任務中僅使用一個同步,可以簡化成同步函數的形式。
(3).4如果使用多個同步(多個鎖),必須使用同步代碼塊。
三、單例模式懶漢式中併發訪問
1、懶漢式並併發訪問,容易出現線程安全問題,而導致對象的不唯一。
2、解決的方式,加上同步關鍵字。如果使用同步代碼塊,使用的鎖是 類名.class
3、但是效率會降低,所以可以使用同步代碼塊,加上雙重對引用變量的判斷。
public static Single getInstance()
{
if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s = new Single();
}
}
return s;
}
四、死鎖:
1、死鎖:線程都爲結束,都處於不執行狀態,這時就稱爲死鎖。
2、常見場景:
(2).1 同步嵌套。
(2).2 都處於凍結狀態。
3、儘量避免死鎖。
4、代碼體現
- //寫一個死鎖
- /*
- 思路:
- 1 所謂的死鎖 就是嵌套循環 相互抱着不是自己的鎖不放就導致了死鎖
- 2 定義一個類 定義兩個鎖
- 3 定義一個類 實現Runnable接口 覆蓋run方法 在寫入一個嵌套循環 再定義一個標記
- 4 開啓線程
- */
- class Demo4
- {
- public static void main(String[] args)
- {
- //創建Ticket類的兩個對象
- Ticket t=new Ticket();
- Ticket t1=new Ticket();
- //分別傳入到兩個Thread類對象的構造函數當中去
- Thread tt=new Thread(t);
- Thread ttt=new Thread(t1);
- //調用start方法
- tt.start();
- //把標記進行轉換
- t.bug=true;
- //再進行調用start方法
- ttt.start();
- }
- }
- //定義了鎖的類
- class MyLock
- {
- //並定義了兩個鎖
- public static final Object locka=new Object();
- public static final Object lockb=new Object();
- }
- //自定義一個Ticket類並實現Runnabale接口
- class Ticket implements Runnable
- {
- //定義一個變量,來記住票的數量
- private int ticket;
- //定義一個變量標記
- boolean bug=false;
- //覆蓋run方法
- public void run()
- {
- //進行判斷標記
- if (bug)
- {
- //進行循環
- while (true)
- {
- //定義一同步代碼塊並傳入MyLock.locka鎖
- synchronized(MyLock.locka)
- {
- //進行對線程sleep100毫秒,並對異常進行處理
- try{Thread.sleep(100);}catch(InterruptedException ie){}
- //打印票的數量
- System.out.println("......................run1111111,,,,,,,,,locka................"+ticket++);
- //在定義一個同步代碼塊這時是MyLock.lockb鎖
- synchronized(MyLock.lockb)
- {
- //進行打印
- System.out.println(".......1111111lockb................");
- }
- }
- }
- }//當標記變化時,就執行下列代碼
- else
- {
- while (true)
- {
- //定義一同步代碼塊並傳入MyLock.lockb鎖
- synchronized(MyLock.lockb)
- {
- //進行對線程sleep100毫秒,並對異常進行處理
- try{Thread.sleep(100);}catch(InterruptedException ie){}
- //打印票的數量
- System.out.println("...............,,lockb22222222222222................"+ticket++);
- //在定義一個同步代碼塊這時是MyLock.locka鎖
- synchronized(MyLock.locka)
- {
- //進行打印
- System.out.println(".....................22222222222run...locka................");
- }
- }
- }
- }
- }
- }
第五節:線程間的通信
一、簡述:
1、多線程間通信:多個線程執行任務不同,但是處理資源相同。
2、等待喚醒機制涉及的方法:
(1)、wait(): 讓線程處於凍結狀態,被wait的線程會被存儲到線程池中。
(2)、notify():喚醒線程池中一個線程(任意)。
(3)、notifyAll():喚醒線程池中的所有線程。
3、這些方法都必須定義在同步中?
因爲這些方法是用於操作線程狀態的方法,必須要明確到底操作的是哪個鎖上的線程。
4、爲什麼操作線程的方法wait notify notifyAll定義在了Object類中?
因爲這些方法是監視器的方法。監視器其實就是鎖,鎖可以是任意的對象,任意的對象調用的方式一定定義在Object類中。
5、一對一 代碼體現
- //線程間的通信 例如生產者與消費者 一對一的情況
- /*
- 思路:
- 1 資源是唯一的 所以創造一個類用來描述這個類
- 2 用一個類來描述生產者生產者生產一個
- 3 用一個類來描述消費者消費者就消費一個
- */
- class Demo5
- {
- public static void main(String[] args)
- {
- Socure s=new Socure();
- Procity p=new Procity(s);
- Take t=new Take(s);
- new Thread(p).start();
- new Thread(t).start();
- }
- }
- //自定義一個資源類
- class Socure
- {
- //私有變量標記和生產和消費的數量
- private boolean balg;
- private int count;
- //定義一個方法用來生產的
- public void add()
- {
- //定義一個同步代碼塊
- synchronized(this)
- {
- //循環標記
- while(balg)
- //進行等待,並對異常進行處理
- try{this.wait();}catch(InterruptedException io){}
- //數量進行加一
- count++;
- //打印所生產的數量
- System.out.println(Thread.currentThread().getName()+"........生產者............."+count);
- //標記進行改變
- balg=true;
- //進行釋放
- this.notify();
- }
- }
- //定義一個方法進行消費
- public void get()
- {
- //定義一個同步代碼塊
- synchronized(this)
- {
- //循環標記
- while(!balg)
- //進行等待,並對異常進行處理
- try{this.wait();}catch(InterruptedException ie){}
- //打印出消費者消費的數量
- System.out.println(Thread.currentThread().getName()+"...............消費者.........."+count);
- //標記進行轉變
- balg=false;
- //進行釋放
- this.notify();
- }
- }
- }
- //定義一個生產者類去實現Runnable接口
- class Procity implements Runnable
- {
- //定義資源的變量
- private Socure s;
- Procity(Socure s)
- {
- this.s=s;
- }
- //覆蓋run方法
- public void run()
- {
- while (true)
- {
- //進行生產
- s.add();
- }
- }
- }
- //定義一個消費者類實現Runnavale接口
- class Take implements Runnable
- {
- //定義資源的變量
- private Socure s;
- Take(Socure s)
- {
- this.s=s;
- }
- //覆蓋run方法
- public void run()
- {
- while (true)
- {
- //進行消費
- s.get();
- }
- }
- }
6、多對多 代碼體現
- //線程之間的通信 多對多的情況
- class Demo6
- {
- public static void main(String[] args)
- {
- //創建子類類的對象
- Recour r=new Recour();
- //定義生產者,並把資源進行傳入構造函數當中去
- Procity p=new Procity(r);
- //定義生消費者,並把資源進行傳入構造函數當中去
- Take t=new Take(r);
- //把生產者傳入到Thread類對象中的構造方法並調用start方法
- new Thread(p).start();
- new Thread(p).start();
- //把消費者傳入到Thread類對象中的構造方法並調用start方法
- new Thread(t).start();
- new Thread(t).start();
- }
- }
- //自定義一個資源類
- class Recour
- {
- //定義一個變量用來記住生產和消費的個數
- private int count;
- //定義一個標記
- private boolean flag;
- //定義一個方法 進行生產的
- public void add()
- {
- //同步代碼塊
- synchronized(this)
- {
- //定義一個循環
- while(flag)
- //如果符合進行等待,並對異常進行處理
- try{this.wait();}catch(InterruptedException io){}
- //生產的個數
- count++;
- //打印出生產的個數
- System.out.println(Thread.currentThread().getName()+"......生產者......."+count);
- //轉換標記
- flag=true;
- //進行釋放
- this.notifyAll();
- }
- }
- public void get()
- {
- //同步代碼塊
- synchronized(this)
- {
- //定義一個循環
- while (!flag)
- //如果符合進行等待,並對異常進行處理
- try{this.wait();}catch(InterruptedException ioe){}
- //打印出消費的個數
- System.out.println(Thread.currentThread().getName()+"..消費者...."+count);
- //轉換標記
- flag=false;
- //進行釋放
- this.notifyAll();
- }
- }
- }
- //自定義一個生產類去實現Runnable接口
- class Procity implements Runnable
- {
- //定義一個資源類的變量
- private Recour s;
- Procity(Recour s)
- {
- this.s=s;
- }
- //覆蓋run方法
- public void run()
- {
- while (true)
- {
- //進行生產
- s.add();
- }
- }
- }
- //定義一個消費類並且去實現Runnable接口
- class Take implements Runnable
- {
- //定義一個資源類的變量
- private Recour s;
- Take(Recour s)
- {
- this.s=s;
- }
- //覆蓋run方法
- public void run()
- {
- while (true)
- {
- //進行消費
- s.get();
- }
- }
- }
二、jdk1.5中提供了多線程升級解決方案:
1、jdk1.5以後將同步和鎖封裝成了對象。並將操作鎖的隱式方式定義到了該對象中,將隱式動作變成了顯示動作。
2、Lock接口: 出現替代了同步代碼塊或者同步函數。將同步的隱式鎖操作變成現實鎖操作。同時更爲靈活。可以一個鎖上加上多組監視器。
3、Lock接口中的方法:
(1)、lock():獲取鎖。
(2)、unlock():釋放鎖,通常需要定義finally代碼塊中。
4、Condition接口:出現替代了Object中的wait notify notifyAll方法。
(1)、將這些監視器方法單獨進行了封裝,變成Condition監視器對象。可以任意鎖進行組合。
5、Condition接口中的方法:
(1)、await():讓線程處於凍結狀態
(2)、signal():喚醒線程池中一個線程(任意)。
(3)、signalAll(): 喚醒線程池中的所有線程。
6、代碼體現三、停止線程:
1、stop方法:此方法以過時,通過該方法的描述得到解決辦法。
2、run方法結束:任務中都會有循環結構,只要控制住循環就可以結束任務,控制循環通常就用定義標記來完成。
3、如果線程處於了凍結狀態,無法讀取標記。如何結束呢?
(1)、可以使用Thread類中的interrupt()方法將線程從凍結狀態強制恢復到運行狀態中來,讓線程具備cpu的執行資格。
注:當時強制動作會發生了InterruptedException,記得要處理 。
4、sleep和wait方法的異同點:
(1)、相同點:可以讓線程處於凍結狀態。
(2)、不同點:
(2).1:
sleep必須指定時間。
wait可以指定時間,也可以不指定時間。
(2).2:
sleep時間到,線程處於臨時阻塞或者運行。
wait如果沒有時間,必須要通過notify或者notifyAll喚醒。
(2).3:
sleep不一定非要定義在同步中。
wait必須定義在同步中。
(2).4:
都定義在同步中,
線程執行到sleep,不會釋放鎖。
線程執行到wait,會釋放鎖。
四、多線程中的其他方法:
1、setDaemon: 將線程設置爲守護線程,必須在開啓前設置,當進程中的線程都是守護線程時,進程結束。
2、setPriority()方法用來設置優先級
(2).1 MAX_PRIORITY 最高優先級10。
(2).2 MIN_PRIORITY 最低優先級。
(2).3 NORM_PRIORITY 分配給線程的默認優先級。
3、join方法:加入一個執行線程。
當a線程執行到了b線程的.join()方法時,a線程就會等待,等b線程都執行完,a線程纔會執行。(此時b和其他線程交替運行。)join可以用來臨時加入線程執行。
4、yield方法:暫停線程,釋放執行權。
轉自:http://blog.csdn.net/bei__kou/article/details/46455597?ref=myread