面试和学习必备--Java多线程
目錄
一:多線程的基本概念
1.1什么是進程?
1.2什么是線程?
1.3多進程有什么作用?
1.4多線程有什么作用?
1.5java 程序的運行原理?
二:線程的創建和啟動
2.1繼承Thread類
2.2實現Runable接口
三:線程的生命周期
四:線程的調度和控制
4.1線程優先級
4.2Thread.sleep
4.3Thread.yield
4.4t.join(成員方法)
4.5t.interrupt(中斷)
4.6如何正確的停止一個線程
五:線程的同步
1.為什么需要線程同步
2.使用線程同步
六:synchronized關鍵字的使用
6.1同步代碼塊
6.2在實例方法上使用synchorized和在靜態方法上使用synchronized
6.3面試題:深入synchronized
一:多線程的基本概念
線程指進程中的一個執行場景,也就是執行流程,那么進程和線程有什么區別呢?1.1什么是進程?
一個進程對應一個應用程序。例如:在 windows 操作系統啟動 Word 就表示啟動了一個 進程。在 java 的開發環境下啟動 JVM,就表示啟動了一個進程。現代的計算機都是支持多 進程的,在同一個操作系統中,可以同時啟動多個進程。1.2什么是線程?
線程是一個進程中的執行場景。一個進程可以啟動多個線程。 簡單來說: 進程是一個應用程序(軟件),線程是一個進程中執行場景或者是執行單元,一個進程可以啟動多個線程1.3多進程有什么作用?
單進程計算機(單核CPU)同一時間只能做一件事情。 對于單核計算機來講,在同一個時間點上,游戲進程和音樂進程是同時在運行嗎?不是。 因為計算機的 CPU 只能在某個時間點上做一件事。由于計算機將在“游戲進程”和“音樂 進程”之間頻繁的切換執行,切換速度極高,人類感覺游戲和音樂在同時進行。 多進程的作用不是提高執行速度,而是提高 CPU 的使用率。 進程和進程之間的內存是獨立的。 單核CUP不能真正做到多線程并發,但是可以給人一種“多線程并發”的錯覺,由于CUP的處理速度非常快,多個線程之間頻繁切換執行,給人的感覺是,多個事情同時在做。1.4多線程有什么作用?
多線程不是為了提高執行速度,而是提高應用程序的使用率。 在java中線程和線程共享“堆內存和方法區內存”,棧內存是獨立的,一個線程一個棧。 可以給現實世界中的人類一種錯覺:感覺多個線程在同時并發執行。1.5java 程序的運行原理?
java 命令會啟動 java 虛擬機,啟動 JVM,等于啟動了一個應用程序,表示啟動了一個 進程。該進程會自動啟動一個“主線程”,然后主線程去調用某個類的 main 方法。所以 main 方法運行在主線程中。在此之前的所有程序都是單線程的。二:線程的創建和啟動
Java 虛擬機的主線程入口是 main 方法,用戶可以自己創建線程,創建方式有兩種: ??????? 1.繼承 Thread 類 ??????? 2.實現 Runnable 接口(推薦使用 Runnable 接口)2.1繼承Thread類
采用 Thread 類創建線程,用戶只需要繼承 Thread,覆蓋 Thread 中的 run 方法,父類 Thread中的 run 方法沒有拋出異常,那么子類也不能拋出異常,最后采用 start 啟動線程即可 /*在java語言中實現多線程的第一種方式:第一步:繼承java.lang.Thread;第二步:重寫run方法.三個知識點:如何定義線程?如何創建線程?如何啟動線程? */ public class ThreadTest02 {public static void main(String[] args){//創建線程Thread t = new Processor();//啟動t.start(); //這段代碼執行瞬間結束。告訴JVM再分配一個新的棧給t線程.//run不需要程序員手動調用,系統線程啟動之后自動調用run方法.//t.run(); //這是普通方法調用,這樣做程序只有一個線程,run方法結束之后,下面程序才能繼續執行。//這段代碼在主線程中運行.for(int i=0;i<10;i++){System.out.println("main-->" + i);}//有了多線程之后,main方法結束只是主線程棧中沒有方法棧幀了。//但是其他線程或者其他棧中還有棧幀。//main方法結束,程序可能還在運行。} }//定義一個線程 class Processor extends Thread {//重寫run方法public void run(){for(int i=0;i<30;i++){System.out.println("run-->" + i);}} }2.2實現Runable接口
其實 Thread 對象本身就實現了 Runnable 接口,但一般建議直接使用 Runnable 接口來寫多線程程序,因為接口會比類帶來更多的好處。 /*java中實現線程的第二種方式:第一步:寫一個類實現java.lang.Runnable;接口第二步:實現run方法. */ public class ThreadTest03 {public static void main(String[] args){//創建線程Thread t = new Thread(new Processor1());//啟動t.start();} }//這種方式是推薦的。因為一個類實現接口之外保留了類的繼承。 class Processor1 implements Runnable {public void run(){for(int i=0;i<10;i++){System.out.println("run-->"+i);}} }三:線程的生命周期
線程的生命周期存在五個狀態:新建、就緒、運行、阻塞、死亡四:線程的調度和控制
通常我們的計算機只有一個 CPU,CPU 在某一個時刻只能執行一條指令,線程只有得到 CPU時間片,也就是使用權,才可以執行指令。在單 CPU 的機器上線程不是并行運行的,只有 在多個 CPU 上線程才可以并行運行。Java 虛擬機要負責線程的調度,取得 CPU 的使用權,目前有兩種調度模型:分時調度模型和搶占式調度模型,Java 使用搶占式調度模型。 ??????? 1.分時調度模型:所有線程輪流使用 CPU 的使用權,平均分配每個線程占用 CPU 的時間片。 ??????? 2.搶占式調度模型:優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那么會隨機選擇一個,優先級高的線程獲取的 CPU 時間片相對多一些。4.1線程優先級
線 程 優 先 級 主 要 分 三 種 : ????????MAX_PRIORITY( 最 高 級 ) ????????MIN_PRIORITY (最低級) ????????NORM_PRIORITY(標準)默認 /*線程優先級高的獲取的CPU時間片相對多一些。優先級:1-10最低 1最高 10默認 5 */ public class ThreadTest05 {public static void main(String[] args){System.out.println(Thread.MAX_PRIORITY); //10System.out.println(Thread.MIN_PRIORITY); //1System.out.println(Thread.NORM_PRIORITY); //5Thread t1 = new Processor3();t1.setName("t1");Thread t2 = new Processor3();t2.setName("t2");System.out.println(t1.getPriority()); //5System.out.println(t2.getPriority()); //5//設置優先級t1.setPriority(5);t2.setPriority(6);//啟動線程t1.start();t2.start();} }/****/ class Processor3 extends Thread {public void run(){for(int i=0;i<50;i++){System.out.println(Thread.currentThread().getName()+"--->" + i);}} } 從以上輸出結果應該看可以看出,優先級高的線程會得到的 CPU 時間多一些,優先執行完成.即優先級越高的線程搶占到cup時間片的概率更高,所以執行的時候機會更多,更率先完成任務。
4.2Thread.sleep
sleep 設置休眠的時間,單位毫秒,當一個線程遇到 sleep 的時候,就會睡眠,進入到阻塞狀態,放棄 CPU,騰出 cpu 時間片,給其他線程用,所以在開發中通常我們會這樣做,使其他 的線程能夠取得 CPU 時間片,當睡眠時間到達了,線程會進入可運行狀態,得到 CPU 時間片繼續執行,如果線程在睡眠狀態被中斷了,將會拋出 IterruptedException異常。 package com.xxx.thread.chapter09;/*1.Thread.sleep(毫秒);2.sleep方法是一個靜態方法.3.該方法的作用:阻塞當前線程.騰出CPU,讓給其他線程。 */ public class ThreadTest06 {public static void main(String[] args) throws InterruptedException{Thread t1 = new Processor4();t1.setName("t1");t1.start();//阻塞主線程for(int i=0;i<10;i++){System.out.println(Thread.currentThread().getName()+"--->"+i);Thread.sleep(500);}} }class Processor4 extends Thread {//Thread中的run方法不拋出異常,所以重寫run方法之后,在run方法的聲明位置上不能使用throws//所以run方法中的異常只能try...catch...//方法重寫:返回值 方法名 方法參數必須一致,子類重寫方法的訪問權限要比父類的大,//子類不能拋出比父類重寫方法范圍更大的異常和不能拋出父類重寫方法沒有的異常public void run(){for(int i=0;i<10;i++){System.out.println(Thread.currentThread().getName()+"--->"+i);try{Thread.sleep(1000); //讓當前線程阻塞1S。}catch(InterruptedException e){e.printStackTrace();}}}}4.3Thread.yield
它與 sleep()類似,只是不能由用戶指定暫停多長時間,并且 yield()方法只能讓同優先級的線程有執行的機會. 原理:讓當前線程放棄當前搶奪到的時間片,然后回到就緒狀態,重新去搶奪時間片,如果在就緒狀態中依舊是當前線程搶奪到了時間片,那么就依舊是當前線程執行。 /*Thread.yield();1.該方法是一個靜態方法.2.作用:給同一個優先級的線程讓位。但是讓位時間不固定。3.和sleep方法相同,就是yield時間不固定。 */ public class ThreadTest10 {public static void main(String[] args){Thread t = new Processor8();t.setName("t");t.start();//主線程中for(int i=0;i<100;i++){System.out.println(Thread.currentThread().getName()+"-->"+i);}} }class Processor8 extends Thread {public void run(){for(int i=0;i<100;i++){System.out.println(Thread.currentThread().getName()+"-->"+i);if(i%20==0){Thread.yield();}}} }4.4t.join(成員方法)
當前線程可以調用另一個線程的 join 方法,調用后當前線程會被阻塞不再執行,直到被調 用的線程執行完畢,當前線程才會執行 package com.xxx.thread.chapter09;/*線程的合并 */ public class ThreadTest11 {public static void main(String[] args) throws Exception{Thread t = new Thread(new Processor9());t.setName("t");t.start();//合并線程t.join(); //t和主線程合并. 單線程的程序.//主線程for(int i=0;i<10;i++){System.out.println(Thread.currentThread().getName()+"-->"+i);}} }class Processor9 implements Runnable {public void run(){for(int i=0;i<5;i++){try{Thread.sleep(1000);}catch(InterruptedException e){}System.out.println(Thread.currentThread().getName()+"-->"+i);}} }4.5t.interrupt(中斷)
如果我們的線程正在睡眠,可以采用 interrupt 進行中斷 /*某線程正在休眠,如果打斷它的休眠.以下方式依靠的是異常處理機制。 */ public class ThreadTest08 {public static void main(String[] args) throws Exception{//需求:啟動線程,5S之后打斷線程的休眠.Thread t = new Thread(new Processor6());//起名t.setName("t");//啟動t.start();//5S之后Thread.sleep(5000);//打斷t的休眠.t.interrupt();} }class Processor6 implements Runnable {public void run(){try{Thread.sleep(100000000000L);System.out.println("HelloWorld!");}catch(InterruptedException e){//e.printStackTrace();} for(int i=0;i<10;i++){System.out.println(Thread.currentThread().getName()+"-->"+i);}} }4.6如何正確的停止一個線程
通常定義一個標記,來判斷標記的狀態停止線程的執行 /*如何正確的更好的終止一個正在執行的線程.需求:線程啟動5S之后終止。 */ public class ThreadTest09 {public static void main(String[] args) throws Exception{Processor7 p = new Processor7();Thread t = new Thread(p);t.setName("t");t.start();//5S之后終止.Thread.sleep(5000);//終止p.run = false;} }class Processor7 implements Runnable {boolean run = true;public void run(){for(int i=0;i<10;i++){//給需要執行的線程的主體代碼設置一個開關if(run){try{Thread.sleep(1000);}catch(Exception e){}System.out.println(Thread.currentThread().getName()+"-->" + i);}else{return;}}} }五:線程的同步
a.線程模型分為:異步編程模型和同步編程模型
????????異步編程模型:
????????????????線程t1和線程t2,各自執行各自的任務,誰也不用等誰,這種編程模型就叫異步編程模型。
????????同步編程模型:
????????????????線程t1和線程t2,執行的時候,需要排隊執行,兩個線程之間發生了等待關系,這就是同步編程模型。
????????? 異步就是并發,同步就是排隊
b.什么條件下數據在多線程并發的環境下會存在安全問題?
? 三個條件
滿足以上三個條件就可能會存在安全問題
c.怎么解決線程安全問題:
線程排隊執行,不能并發,用排隊執行解決線程安全問題,這種機制稱為“線程同步機制”
5.1.為什么需要線程同步
多線程帶來的安全問題:
假設沒有對線程進行處理,依舊是多線程并發:有兩個人同時對一張銀行卡進行取錢操作,當第一個人取了錢由于網絡波動沒有及時更新余額,這時另一個人也對該銀行卡執行取錢操作,由于第一個人取了錢余額沒有更新,這時候錢總數是不變的,當第二個人取了同樣多的錢,之后賬戶上只會顯示變了一份的錢,但實際上卻取了兩份錢,這就會出現數據錯誤。
5.2.使用線程同步
線程同步,指某一個時刻,指允許一個線程來訪問共享資源,線程同步其實是對對象加鎖, 如果對象中的方法都是同步方法,那么某一時刻只能執行一個方法,采用線程同步解決以上 的問題,我們只要保證線程一操作 s 時,線程 2 不允許操作即可,只有線程一使用完成 s 后, 再讓線程二來使用 s 變量。
使用線程同步是通過synchronized關鍵字實現的。六:synchronized關鍵字的使用
synchorized有三種寫法:
一:同步代碼塊:靈活
synchorized(線程共享對象){}
二:在實例方法上使用synchorize
?????? 表示共享對象一定是this
?????? 并且同步代碼塊是整個方法體
??
三:在靜態方法上使用synchorized
????? 表示類鎖
????? 類鎖永遠只有一把
????? 就算創建100個對對象,類鎖也只有一把
6.1同步代碼塊
package com.xxx.thread.chapter09;/*以下程序使用線程同步機制保證數據的安全。 */ public class ThreadTest13 {public static void main(String[] args){//創建一個公共的賬戶Account act = new Account("actno-001",5000.0);//創建線程對同一個賬戶取款Thread t1 = new Thread(new Processor11(act));Thread t2 = new Thread(new Processor11(act));t1.start();t2.start();} }//取款線程 class Processor11 implements Runnable {//賬戶Account act;//ConstructorProcessor11(Account act){this.act = act;}public void run(){act.withdraw(1000.0);System.out.println("取款1000.0成功,余額:" + act.getBalance());} }//賬戶 class Account2 {private String actno;private double balance;public Account2(){}public Account2(String actno,double balance){this.actno = actno;this.balance = balance;}//setter and getterpublic void setActno(String actno){this.actno = actno;}public void setBalance(double balance){this.balance = balance;}public String getActno(){return actno;}public double getBalance(){return balance;}//對外提供一個取款的方法public void withdraw(double money){ //對當前賬戶進行取款操作//把需要同步的代碼,放到同步語句塊中./*原理:t1線程和t2線程.t1線程執行到此處,遇到了synchronized關鍵字,就會去找this的對象鎖,如果找到this對象鎖,則進入同步語句塊中執行程序。當同步語句塊中的代碼執行結束之后,t1線程歸還this的對象鎖。在t1線程執行同步語句塊的過程中,如果t2線程也過來執行以下代碼,也遇到synchronized關鍵字,所以也去找this的對象鎖,但是該對象鎖被t1線程持有,只能在這等待this對象的歸還。synchronized后面的這個小括號中傳遞的數據非常重要,這個數據是多線程共享的數據,只有共享才能達多線程排隊java語言都要一把鎖,其實這把鎖就是一個標記100個對象100把鎖*/synchronized(this){double after = balance - money;//延遲try{Thread.sleep(1000);}catch(Exception e){}//更新this.setBalance(after);}} }6.2在實例方法上使用synchorized和在靜態方法上使用synchronized
package com.xxx.thread.chapter09;/*以下程序使用線程同步機制保證數據的安全。 */ public class ThreadTest14 {public static void main(String[] args){//創建一個公共的賬戶Account3 act = new Account3("actno-001",5000.0);//創建線程對同一個賬戶取款Thread t1 = new Thread(new Processor12(act));Thread t2 = new Thread(new Processor12(act));t1.start();t2.start();} }//取款線程 class Processor12 implements Runnable {//賬戶Account3 act;//ConstructorProcessor12(Account3 act){this.act = act;}public void run(){act.withdraw(1000.0);System.out.println("取款1000.0成功,余額:" + act.getBalance());} }//賬戶 class Account3 {private String actno;private double balance;public Account3(){}public Account3(String actno,double balance){this.actno = actno;this.balance = balance;}//setter and getterpublic void setActno(String actno){this.actno = actno;}public void setBalance(double balance){this.balance = balance;}public String getActno(){return actno;}public double getBalance(){return balance;}//對外提供一個取款的方法//synchronized關鍵字添加到成員方法上,線程拿走的默認是this的對象鎖。//synchorized(this){}//synchorized出現在方法上,表示整個方法需要同步,可能會無故擴大程序同步的范圍public synchronized void withdraw(double money){ //對當前賬戶進行取款操作//把需要同步的代碼,放到同步語句塊中./*原理:t1線程和t2線程.t1線程執行到此處,遇到了synchronized關鍵字,就會去找this的對象鎖,如果找到this對象鎖,則進入同步語句塊中執行程序。當同步語句塊中的代碼執行結束之后,t1線程歸還this的對象鎖。在t1線程執行同步語句塊的過程中,如果t2線程也過來執行以下代碼,也遇到synchronized關鍵字,所以也去找this的對象鎖,但是該對象鎖被t1線程持有,只能在這等待this對象的歸還。*/double after = balance - money;//延遲try{Thread.sleep(1000);}catch(Exception e){}//更新this.setBalance(after);}}6.3面試題:深入synchronized
1.有無synchronized的代碼執行順序: 當執行沒有添加synchronized的關鍵字的方法的時候不用考慮是否相同的數據對象是否被加鎖了,不能執行需要等待的問題。 package com.xxx.thread.chapter09;/*面試題 */ public class ThreadTest15 {public static void main(String[] args) throws Exception{MyClass mc = new MyClass();Processor13 p = new Processor13(mc);Thread t1 = new Thread(p);t1.setName("t1");Thread t2 = new Thread(p);t2.setName("t2");//啟動線程t1.start();//延遲(保證t1線程先啟動,并執行run)Thread.sleep(1000);t2.start();} }class Processor13 implements Runnable {MyClass mc;Processor13(MyClass mc){this.mc = mc;}public void run(){if(Thread.currentThread().getName().equals("t1")){mc.m1();}if(Thread.currentThread().getName().equals("t2")){mc.m2();}} }class MyClass {public synchronized void m1(){//休眠try{Thread.sleep(10000);}catch(Exception e){}System.out.println("m1....");}/*//m2方法的執行不需要等m1的結束,因為m2方法上沒有synchronizedpublic void m2(){System.out.println("m2....");}*///m2方法會等m1方法結束,t1,t2共享同一個mc,并且m1和m2方法上都有synchronizedpublic synchronized void m2(){System.out.println("m2....");} }2.是否共享一個相同的數據對象
??????? 當synchronized(共享數據對象){},其中的“共享數據對象”不相同是不用考慮鎖得問題,因為不是相同的數據對象。
package com.xxx.thread.chapter09;/*面試題:synchronized(共享數據對象){} */ public class ThreadTest16 {public static void main(String[] args) throws Exception{MyClass1 mc1 = new MyClass1();MyClass1 mc2 = new MyClass1();Processor14 p1 = new Processor14(mc1);Processor14 p2 = new Processor14(mc2);Thread t1 = new Thread(p1);t1.setName("t1");Thread t2 = new Thread(p2);t2.setName("t2");//啟動線程t1.start();//延遲(保證t1線程先啟動,并執行run)Thread.sleep(1000);t2.start();} }class Processor14 implements Runnable {MyClass1 mc;Processor14(MyClass1 mc){this.mc = mc;}public void run(){if(Thread.currentThread().getName().equals("t1")){mc.m1();}if(Thread.currentThread().getName().equals("t2")){mc.m2();}} }class MyClass1 {public synchronized void m1(){//休眠try{Thread.sleep(10000);}catch(Exception e){}System.out.println("m1....");}//m2方法不會等m1方法結束,t1,t2不共享同一個mc(對象)public synchronized void m2(){System.out.println("m2....");} }3.區分synchronized在靜態方法上和實例方法上的區別
??????? 對象鎖可以有很多個,但是類鎖之后一個,如果synchronized關鍵字是添加在靜態方法上,則說名這是一個類鎖,因為靜態方法需要類來執行。
??????? 當一個類被鎖住,說明后面創建的任何實例對象對不能再用該類了,除非類鎖的相關代碼執行結束,類鎖被放開。
package com.xxx.thread.chapter09;/*面試題:類鎖,類只有一個,所以鎖是類級別的,只有一個.*/ public class ThreadTest17 {public static void main(String[] args) throws Exception{Thread t1 = new Thread(new Processor17());Thread t2 = new Thread(new Processor17());t1.setName("t1");t2.setName("t2");t1.start();//延遲,保證t1先執行Thread.sleep(1000);t2.start();} }class Processor17 implements Runnable {public void run(){if("t1".equals(Thread.currentThread().getName())){MyClass17.m1();}if("t2".equals(Thread.currentThread().getName())){MyClass17.m2();}} }class MyClass17 {//synchronized添加到靜態方法上,線程執行此方法的時候會找類鎖。public synchronized static void m1(){try{Thread.sleep(10000);}catch(Exception e){}System.out.println("m1....");}//不會等m1結束,因為該方法沒有被synchronized修飾/*public static void m2(){System.out.println("m2...");}*///m2方法等m1結束之后才能執行,該方法有synchronized//線程執行該代碼需要“類鎖”,而類鎖只有一個。public synchronized static void m2(){System.out.println("m2...");} }總結
以上是生活随笔為你收集整理的面试和学习必备--Java多线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Springboot整合freemark
- 下一篇: RabbitMQ消息发送和接收