實例代碼講解JAVA多線程
進程與線程
進程是程序的一次動態(tài)執(zhí)行過程,它需要經(jīng)歷從代碼加載,代碼執(zhí)行到執(zhí)行完畢的一個完整的過程,這個過程也是進程本身從產(chǎn)生,發(fā)展到最終消亡的過程。多進程操作系統(tǒng)能同時達運行多個進程(程序),由于 CPU 具備分時機制,所以每個進程都能循環(huán)獲得自己的CPU 時間片。由于 CPU 執(zhí)行速度非???,使得所有程序好像是在同時運行一樣。
多線程是實現(xiàn)并發(fā)機制的一種有效手段。進程和線程一樣,都是實現(xiàn)并發(fā)的一個基本單位。線程是比進程更小的執(zhí)行單位,線程是進程的基礎之上進行進一步的劃分。所謂多線程是指一個進程在執(zhí)行過程中可以產(chǎn)生多個更小的程序單元,這些更小的單元稱為線程,這些線程可以同時存在,同時運行,一個進程可能包含多個同時執(zhí)行的線程。進程與線程的區(qū)別如圖所示:
Java中線程實現(xiàn)的方式
在 Java 中實現(xiàn)多線程有兩種手段,一種是繼承 Thread 類,另一種就是實現(xiàn) Runnable 接口。下面我們就分別來介紹這兩種方式的使用。
實現(xiàn) Runnable 接口
package ljz; class MyThread implements Runnable{ // 實現(xiàn)Runnable接口,作為線程的實現(xiàn)類 private String name ; // 表示線程的名稱 public MyThread(String name){ this.name = name ; // 通過構(gòu)造方法配置name屬性 } public void run(){ // 覆寫run()方法,作為線程 的操作主體 for(int i=0;i<10;i++){ System.out.println(name + "運行,i = " + i) ; } } }; public class RunnableDemo01{ public static void main(String args[]){ MyThread mt1 = new MyThread("線程A ") ; // 實例化對象 MyThread mt2 = new MyThread("線程B ") ; // 實例化對象 Thread t1 = new Thread(mt1) ; // 實例化Thread類對象 Thread t2 = new Thread(mt2) ; // 實例化Thread類對象 t1.start() ; // 啟動多線程 t2.start() ; // 啟動多線程 } };
程序運行結(jié)果:
繼承 Thread 類
class MyThread extends Thread{ // 繼承Thread類,作為線程的實現(xiàn)類 private String name ; // 表示線程的名稱 public MyThread(String name){ this.name = name ; // 通過構(gòu)造方法配置name屬性 } public void run(){ // 覆寫run()方法,作為線程 的操作主體 for(int i=0;i<10;i++){ System.out.println(name + "運行,i = " + i) ; } } }; public class ThreadDemo02{ public static void main(String args[]){ MyThread mt1 = new MyThread("線程A ") ; // 實例化對象 MyThread mt2 = new MyThread("線程B ") ; // 實例化對象 mt1.start() ; // 調(diào)用線程主體 mt2.start() ; // 調(diào)用線程主體 } };
程序運行結(jié)果:
從程序可以看出,現(xiàn)在的兩個線程對象是交錯運行的,哪個線程對象搶到了 CPU 資源,哪個線程就可以運行,所以程序每次的運行結(jié)果肯定是不一樣的,在線程啟動雖然調(diào)用的是 start() 方法,但實際上調(diào)用的卻是 run() 方法定義的主體。
Thread 類和 Runnable 接口
通過 Thread 類和 Runable 接口都可以實現(xiàn)多線程,那么兩者有哪些聯(lián)系和區(qū)別呢?下面我們觀察 Thread 類的定義。
public class Thread extends Object implements Runnable
從 Thread 類的定義可以清楚的發(fā)現(xiàn),Thread 類也是 Runnable 接口的子類,但在Thread類中并沒有完全實現(xiàn) Runnable 接口中的 run() 方法,下面是 Thread 類的部分定義。
Private Runnable target; public Thread(Runnable target,String name){ init(null,target,name,0); } private void init(ThreadGroup g,Runnable target,String name,long stackSize){ ... this.target=target; } public void run(){ if(target!=null){ target.run(); } }
從定義中可以發(fā)現(xiàn),在 Thread 類中的 run() 方法調(diào)用的是 Runnable 接口中的 run() 方法,也就是說此方法是由 Runnable 子類完成的,所以如果要通過繼承 Thread 類實現(xiàn)多線程,則必須覆寫 run()。
實際上 Thread 類和 Runnable 接口之間在使用上也是有區(qū)別的,如果一個類繼承 Thread類,則不適合于多個線程共享資源,而實現(xiàn)了 Runnable 接口,就可以方便的實現(xiàn)資源的共享。
線程的狀態(tài)變化
要想實現(xiàn)多線程,必須在主線程中創(chuàng)建新的線程對象。任何線程一般具有5種狀態(tài),即創(chuàng)建,就緒,運行,阻塞,終止。下面分別介紹一下這幾種狀態(tài):
1 創(chuàng)建狀態(tài)
在程序中用構(gòu)造方法創(chuàng)建了一個線程對象后,新的線程對象便處于新建狀態(tài),此時它已經(jīng)有了相應的內(nèi)存空間和其他資源,但還處于不可運行狀態(tài)。新建一個線程對象可采用Thread 類的構(gòu)造方法來實現(xiàn),例如 “Thread thread=new Thread()”。
2 就緒狀態(tài)
新建線程對象后,調(diào)用該線程的 start() 方法就可以啟動線程。當線程啟動時,線程進入就緒狀態(tài)。此時,線程將進入線程隊列排隊,等待 CPU 服務,這表明它已經(jīng)具備了運行條件。
3 運行狀態(tài)
當就緒狀態(tài)被調(diào)用并獲得處理器資源時,線程就進入了運行狀態(tài)。此時,自動調(diào)用該線程對象的 run() 方法。run() 方法定義該線程的操作和功能。
4 阻塞狀態(tài)
一個正在執(zhí)行的線程在某些特殊情況下,如被人為掛起或需要執(zhí)行耗時的輸入/輸出操作,會讓 CPU 暫時中止自己的執(zhí)行,進入阻塞狀態(tài)。在可執(zhí)行狀態(tài)下,如果調(diào)用sleep(),suspend(),wait() 等方法,線程都將進入阻塞狀態(tài),發(fā)生阻塞時線程不能進入排隊隊列,只有當引起阻塞的原因被消除后,線程才可以轉(zhuǎn)入就緒狀態(tài)。
5 死亡狀態(tài)
線程調(diào)用 stop() 方法時或 run() 方法執(zhí)行結(jié)束后,即處于死亡狀態(tài)。處于死亡狀態(tài)的線程不具有繼續(xù)運行的能力。
在此提出一個問題,Java 程序每次運行至少啟動幾個線程?
回答:至少啟動兩個線程,每當使用 Java 命令執(zhí)行一個類時,實際上都會啟動一個 JVM,每一個JVM實際上就是在操作系統(tǒng)中啟動一個線程,Java 本身具備了垃圾的收集機制。所以在 Java 運行時至少會啟動兩個線程,一個是 main 線程,另外一個是垃圾收集線程。
取得和設置線程的名稱
class MyThread implements Runnable{ //實現(xiàn)Runnable接口 public void run(){ for(int i=0;i<3;i++){ System.Out.Println(Thread.currentThread().getName()+"運行, i="+i); //取得當前線程的名稱 } } }; public class ThreadDemo{ public static void main(String args[]){ MyThread my=new MyThread(); //定義Runnable子類對象 new Thread(my).start; //系統(tǒng)自動設置線程名稱 new Thread(my,"線程A").start(); //手工設置線程名稱 } };
程序運行結(jié)果:
線程的操作方法
剛才在分析自定義模式工作原理的時候其實就已經(jīng)提到了,如果想要更改Glide的默認配
線程的強制運行
在線程操作中,可以使用 join() 方法讓一個線程強制運行,線程強制運行期間,其他線程無法運行,必須等待此線程完成之后才可以繼續(xù)執(zhí)行。
class MyThread implements Runnable{ // 實現(xiàn)Runnable接口 public void run(){ // 覆寫run()方法 for(int i=0;i<50;i++){ System.out.println(Thread.currentThread().getName() + "運行,i = " + i) ; // 取得當前線程的名字 } } }; public class ThreadJoinDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 實例化Runnable子類對象 Thread t = new Thread(mt,"線程"); // 實例化Thread對象 t.start() ; // 啟動線程 for(int i=0;i<50;i++){ if(i>10){ try{ t.join() ; // 線程強制運行 }catch(InterruptedException e){ } } System.out.println("Main線程運行 --> " + i) ; } } };
程序運行結(jié)果:
線程的休眠
在程序中允許一個線程進行暫時的休眠,直接使用 Thread.sleep() 即可實現(xiàn)休眠。
class MyThread implements Runnable{ // 實現(xiàn)Runnable接口 public void run(){ // 覆寫run()方法 for(int i=0;i<50;i++){ try{ Thread.sleep(500) ; // 線程休眠 }catch(InterruptedException e){ } System.out.println(Thread.currentThread().getName() + "運行,i = " + i) ; // 取得當前線程的名字 } } }; public class ThreadSleepDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 實例化Runnable子類對象 Thread t = new Thread(mt,"線程"); // 實例化Thread對象 t.start() ; // 啟動線程 } };
程序運行結(jié)果:
中斷線程
當一個線程運行時,另外一個線程可以直接通過interrupt()方法中斷其運行狀態(tài)。
class MyThread implements Runnable{ // 實現(xiàn)Runnable接口 public void run(){ // 覆寫run()方法 System.out.println("1、進入run()方法") ; try{ Thread.sleep(10000) ; // 線程休眠10秒 System.out.println("2、已經(jīng)完成了休眠") ; }catch(InterruptedException e){ System.out.println("3、休眠被終止") ; return ; // 返回調(diào)用處 } System.out.println("4、run()方法正常結(jié)束") ; } }; public class ThreadInterruptDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 實例化Runnable子類對象 Thread t = new Thread(mt,"線程"); // 實例化Thread對象 t.start() ; // 啟動線程 try{ Thread.sleep(2000) ; // 線程休眠2秒 }catch(InterruptedException e){ System.out.println("3、休眠被終止") ; } t.interrupt() ; // 中斷線程執(zhí)行 } };
程序運行結(jié)果:
后臺線程
在 Java 程序中,只要前臺有一個線程在運行,則整個 Java 進程都不會消失,所以此時可以設置一個后臺線程,這樣即使 Java 線程結(jié)束了,此后臺線程依然會繼續(xù)執(zhí)行,要想實現(xiàn)這樣的操作,直接使用 setDaemon() 方法即可。
class MyThread implements Runnable{ // 實現(xiàn)Runnable接口 public void run(){ // 覆寫run()方法 while(true){ System.out.println(Thread.currentThread().getName() + "在運行。") ; } } }; public class ThreadDaemonDemo{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 實例化Runnable子類對象 Thread t = new Thread(mt,"線程"); // 實例化Thread對象 t.setDaemon(true) ; // 此線程在后臺運行 t.start() ; // 啟動線程 } };
在線程類 MyThread 中,盡管 run() 方法中是死循環(huán)的方式,但是程序依然可以執(zhí)行完,因為方法中死循環(huán)的線程操作已經(jīng)設置成后臺運行。
線程的優(yōu)先級
在 Java 的線程操作中,所有的線程在運行前都會保持在就緒狀態(tài),那么此時,哪個線程的優(yōu)先級高,哪個線程就有可能會先被執(zhí)行。
class MyThread implements Runnable{ // 實現(xiàn)Runnable接口 public void run(){ // 覆寫run()方法 for(int i=0;i<5;i++){ try{ Thread.sleep(500) ; // 線程休眠 }catch(InterruptedException e){ } System.out.println(Thread.currentThread().getName() + "運行,i = " + i) ; // 取得當前線程的名字 } } }; public class ThreadPriorityDemo{ public static void main(String args[]){ Thread t1 = new Thread(new MyThread(),"線程A") ; // 實例化線程對象 Thread t2 = new Thread(new MyThread(),"線程B") ; // 實例化線程對象 Thread t3 = new Thread(new MyThread(),"線程C") ; // 實例化線程對象 t1.setPriority(Thread.MIN_PRIORITY) ; // 優(yōu)先級最低 t2.setPriority(Thread.MAX_PRIORITY) ; // 優(yōu)先級最高 t3.setPriority(Thread.NORM_PRIORITY) ; // 優(yōu)先級最中等 t1.start() ; // 啟動線程 t2.start() ; // 啟動線程 t3.start() ; // 啟動線程 } };
從程序的運行結(jié)果中可以觀察到,線程將根據(jù)其優(yōu)先級的大小來決定哪個線程會先運行,但是需要注意并非優(yōu)先級越高就一定會先執(zhí)行,哪個線程先執(zhí)行將由 CPU 的調(diào)度決定。
線程的禮讓
在線程操作中,也可以使用 yield() 方法將一個線程的操作暫時讓給其他線程執(zhí)行
class MyThread implements Runnable{ // 實現(xiàn)Runnable接口 public void run(){ // 覆寫run()方法 for(int i=0;i<5;i++){ try{ Thread.sleep(500) ; }catch(Exception e){ } System.out.println(Thread.currentThread().getName() + "運行,i = " + i) ; // 取得當前線程的名字 if(i==2){ System.out.print("線程禮讓:") ; Thread.currentThread().yield() ; // 線程禮讓 } } } }; public class ThreadYieldDemo{ public static void main(String args[]){ MyThread my = new MyThread() ; // 實例化MyThread對象 Thread t1 = new Thread(my,"線程A") ; Thread t2 = new Thread(my,"線程B") ; t1.start() ; t2.start() ; } };
程序運行結(jié)果
同步以及死鎖
一個多線程的程序如果是通過 Runnable 接口實現(xiàn)的,則意味著類中的屬性被多個線程共享,那么這樣就會造成一種問題,如果這多個線程要操作同一個資源時就有可能出現(xiàn)資源同步問題。
解決方法:
同步代碼塊
synchronized(同步對象){ 需要同步的代碼 }
class MyThread implements Runnable{ private int ticket = 5 ; // 假設一共有5張票 public void run(){ for(int i=0;i<100;i++){ synchronized(this){ // 要對當前對象進行同步 if(ticket>0){ // 還有票 try{ Thread.sleep(300) ; // 加入延遲 }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println("賣票:ticket = " + ticket-- ); } } } } }; public class SyncDemo02{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定義線程對象 Thread t1 = new Thread(mt) ; // 定義Thread對象 Thread t2 = new Thread(mt) ; // 定義Thread對象 Thread t3 = new Thread(mt) ; // 定義Thread對象 t1.start() ; t2.start() ; t3.start() ; } };
程序運行結(jié)果:
同步方法
除了可以將需要的代碼設置成同步代碼塊外,也可以使用 synchronized 關鍵字將一個方法聲明為同步方法。
synchronized 方法返回值 方法名稱(參數(shù)列表){ }
class MyThread implements Runnable{ private int ticket = 5 ; // 假設一共有5張票 public void run(){ for(int i=0;i<100;i++){ this.sale() ; // 調(diào)用同步方法 } } public synchronized void sale(){ // 聲明同步方法 if(ticket>0){ // 還有票 try{ Thread.sleep(300) ; // 加入延遲 }catch(InterruptedException e){ e.printStackTrace() ; } System.out.println("賣票:ticket = " + ticket-- ); } } }; public class SyncDemo03{ public static void main(String args[]){ MyThread mt = new MyThread() ; // 定義線程對象 Thread t1 = new Thread(mt) ; // 定義Thread對象 Thread t2 = new Thread(mt) ; // 定義Thread對象 Thread t3 = new Thread(mt) ; // 定義Thread對象 t1.start() ; t2.start() ; t3.start() ; } };
程序運行結(jié)果
從程序運行的結(jié)果可以發(fā)現(xiàn),此代碼完成了與之前同步代碼同樣的功能。
死鎖
同步可以保證資源共享操作的正確性,但是過多同步也會產(chǎn)生問題。例如,現(xiàn)在張三想要李四的畫,李四想要張三的書,張三對李四說“把你的畫給我,我就給你書”,李四也對張三說“把你的書給我,我就給你畫”兩個人互相等對方先行動,就這么干等沒有結(jié)果,這實際上就是死鎖的概念。
所謂死鎖,就是兩個線程都在等待對方先完成,造成程序的停滯,一般程序的死鎖都是在程序運行時出現(xiàn)的。
下面以一個簡單范例說明這個概念
class Zhangsan{ // 定義張三類 public void say(){ System.out.println("張三對李四說:“你給我畫,我就把書給你?!?) ; } public void get(){ System.out.println("張三得到畫了。") ; } }; class Lisi{ // 定義李四類 public void say(){ System.out.println("李四對張三說:“你給我書,我就把畫給你”") ; } public void get(){ System.out.println("李四得到書了。") ; } }; public class ThreadDeadLock implements Runnable{ private static Zhangsan zs = new Zhangsan() ; // 實例化static型對象 private static Lisi ls = new Lisi() ; // 實例化static型對象 private boolean flag = false ; // 聲明標志位,判斷那個先說話 public void run(){ // 覆寫run()方法 if(flag){ synchronized(zs){ // 同步張三 zs.say() ; try{ Thread.sleep(500) ; }catch(InterruptedException e){ e.printStackTrace() ; } synchronized(ls){ zs.get() ; } } }else{ synchronized(ls){ ls.say() ; try{ Thread.sleep(500) ; }catch(InterruptedException e){ e.printStackTrace() ; } synchronized(zs){ ls.get() ; } } } } public static void main(String args[]){ ThreadDeadLock t1 = new ThreadDeadLock() ; // 控制張三 ThreadDeadLock t2 = new ThreadDeadLock() ; // 控制李四 t1.flag = true ; t2.flag = false ; Thread thA = new Thread(t1) ; Thread thB = new Thread(t2) ; thA.start() ; thB.start() ; } };
程序運行結(jié)果
以下代碼不再執(zhí)行,程序進入死鎖狀態(tài)。
總結(jié)
至此關于多線程一些基本操作就介紹完了,鑒于筆者經(jīng)驗有限,如果有什么不足和缺漏的地方,歡迎相互交流學習,感謝大家!
以上就是實例代碼講解JAVA多線程的詳細內(nèi)容,更多關于JAVA多線程的資料請關注腳本之家其它相關文章!
- Java多線程中Lock鎖的使用總結(jié)
- Java多線程鎖機制相關原理實例解析
- Java Lambda表達式原理及多線程實現(xiàn)
- Java多線程并發(fā)執(zhí)行demo代碼實例
- 深入分析JAVA 多線程--interrupt()和線程終止方式
- 淺談Java獲得多線程的返回結(jié)果方式(3種)
- Java多線程及線程安全實現(xiàn)方法解析
- Java Lock鎖多線程中實現(xiàn)流水線任務
- 詳解Java Callable接口實現(xiàn)多線程的方式
- Java多線程Callable和Future接口區(qū)別
- Java多線程通信wait()和notify()代碼實例
- Java實現(xiàn)多線程同步五種方法詳解
- Java多線程生產(chǎn)者消費者模式實現(xiàn)過程解析
- 基于Java實現(xiàn)多線程下載并允許斷點續(xù)傳
- Java中的多線程一定就快嗎?
相關文章
SpringBoot基于Disruptor實現(xiàn)高效的消息隊列?
Disruptor是一個開源的Java框架,它被設計用于在生產(chǎn)者-消費者問題上獲得盡量高的吞吐量和盡量低的延遲,本文主要介紹了SpringBoot基于Disruptor實現(xiàn)高效的消息隊列?,具有一定的參考價值,感興趣的可以了解一下2024-02-02Java?SpringBoot項目如何優(yōu)雅的實現(xiàn)操作日志記錄
這篇文章主要介紹了Java?SpringBoot項目如何優(yōu)雅的實現(xiàn)操作日志記錄,文章圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的朋友可以參考一下2022-08-08IntelliJ IDEA 2019.2 x64的安裝、應用與簡單配置(圖文)
這篇文章主要介紹了IntelliJ IDEA 2019.2 x64的安裝、應用與簡單配置,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-10-10關于Spring事務隔離、傳播屬性與@Transactional注解
這篇文章主要介紹了關于事務隔離、Spring傳播屬性與@Transactional注解,如果一組處理步驟或者全部發(fā)生或者一步也不執(zhí)行,我們稱該組處理步驟為一個事務,需要的朋友可以參考下2023-05-05Java異常處理運行時異常(RuntimeException)詳解及實例
這篇文章主要介紹了 Java異常處理運行時異常(RuntimeException)詳解及實例的相關資料,需要的朋友可以參考下http://time.qq.com/?pgv_ref=aiotime2017-05-05Java如何基于ProcessBuilder類調(diào)用外部程序
這篇文章主要介紹了Java如何基于ProcessBuilder類調(diào)用外部程序,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-01-01