Java并发—基础操作
原文地址:Java多線程看這一篇就足夠了(吐血超詳細(xì)總結(jié))
目錄
一、進(jìn)程與線程
二、Java中線程實(shí)現(xiàn)的方式
三、線程的狀態(tài)變化
四、線程的操作方法
五、同步以及死鎖
六、總結(jié)
一、進(jìn)程與線程
進(jìn)程是程序的一次動態(tài)執(zhí)行過程,它需要經(jīng)歷從代碼加載,代碼執(zhí)行到執(zhí)行完畢的一個(gè)完整的過程,這個(gè)過程也是進(jìn)程本身從產(chǎn)生,發(fā)展到最終消亡的過程。多進(jìn)程操作系統(tǒng)能同時(shí)達(dá)運(yùn)行多個(gè)進(jìn)程(程序),由于 CPU 具備分時(shí)機(jī)制,所以每個(gè)進(jìn)程都能循環(huán)獲得自己的CPU 時(shí)間片。由于 CPU 執(zhí)行速度非常快,使得所有程序好像是在同時(shí)運(yùn)行一樣。
多線程是實(shí)現(xiàn)并發(fā)機(jī)制的一種有效手段。進(jìn)程和線程一樣,都是實(shí)現(xiàn)并發(fā)的一個(gè)基本單位。線程是比進(jìn)程更小的執(zhí)行單位,線程是進(jìn)程的基礎(chǔ)之上進(jìn)行進(jìn)一步的劃分。所謂多線程是指一個(gè)進(jìn)程在執(zhí)行過程中可以產(chǎn)生多個(gè)更小的程序單元,這些更小的單元稱為線程,這些線程可以同時(shí)存在,同時(shí)運(yùn)行,一個(gè)進(jìn)程可能包含多個(gè)同時(shí)執(zhí)行的線程。進(jìn)程與線程的區(qū)別如圖所示:?
二、Java中線程實(shí)現(xiàn)的方式
在 Java 中實(shí)現(xiàn)多線程有兩種手段,一種是繼承 Thread 類,另一種就是實(shí)現(xiàn) Runnable 接口。下面我們就分別來介紹這兩種方式的使用。
實(shí)現(xiàn) Runnable 接口
package ljz; class MyThread implements Runnable{ // 實(shí)現(xiàn)Runnable接口,作為線程的實(shí)現(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 + "運(yùn)行,i = " + i) ;}} }; public class RunnableDemo01{public static void main(String args[]){MyThread mt1 = new MyThread("線程A ") ; // 實(shí)例化對象MyThread mt2 = new MyThread("線程B ") ; // 實(shí)例化對象Thread t1 = new Thread(mt1) ; // 實(shí)例化Thread類對象Thread t2 = new Thread(mt2) ; // 實(shí)例化Thread類對象t1.start() ; // 啟動多線程t2.start() ; // 啟動多線程} };程序運(yùn)行結(jié)果:?
繼承 Thread 類
class MyThread extends Thread{ // 繼承Thread類,作為線程的實(shí)現(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 + "運(yùn)行,i = " + i) ;}} }; public class ThreadDemo02{public static void main(String args[]){MyThread mt1 = new MyThread("線程A ") ; // 實(shí)例化對象MyThread mt2 = new MyThread("線程B ") ; // 實(shí)例化對象mt1.start() ; // 調(diào)用線程主體mt2.start() ; // 調(diào)用線程主體} };程序運(yùn)行結(jié)果:?
從程序可以看出,現(xiàn)在的兩個(gè)線程對象是交錯(cuò)運(yùn)行的,哪個(gè)線程對象搶到了 CPU 資源,哪個(gè)線程就可以運(yùn)行,所以程序每次的運(yùn)行結(jié)果肯定是不一樣的,在線程啟動雖然調(diào)用的是 start() 方法,但實(shí)際上調(diào)用的卻是 run() 方法定義的主體。
Thread 類和 Runnable 接口
通過 Thread 類和 Runable 接口都可以實(shí)現(xiàn)多線程,那么兩者有哪些聯(lián)系和區(qū)別呢?下面我們觀察 Thread 類的定義。
public class Thread extends Object implements Runnable從 Thread 類的定義可以清楚的發(fā)現(xiàn),Thread 類也是 Runnable 接口的子類,但在Thread類中并沒有完全實(shí)現(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 類實(shí)現(xiàn)多線程,則必須覆寫 run()。實(shí)際上 Thread 類和 Runnable 接口之間在使用上也是有區(qū)別的,如果一個(gè)類繼承 Thread類,則不適合于多個(gè)線程共享資源,而實(shí)現(xiàn)了 Runnable 接口,就可以方便的實(shí)現(xiàn)資源的共享。
三、線程的狀態(tài)變化
要想實(shí)現(xiàn)多線程,必須在主線程中創(chuàng)建新的線程對象。任何線程一般具有5種狀態(tài),即創(chuàng)建、就緒、運(yùn)行、阻塞、終止。下面分別介紹一下這幾種狀態(tài):
-
創(chuàng)建狀態(tài)?
在程序中用構(gòu)造方法創(chuàng)建了一個(gè)線程對象后,新的線程對象便處于新建狀態(tài),此時(shí)它已經(jīng)有了相應(yīng)的內(nèi)存空間和其他資源,但還處于不可運(yùn)行狀態(tài)。新建一個(gè)線程對象可采用Thread 類的構(gòu)造方法來實(shí)現(xiàn),例如 “Thread thread=new Thread()”。
-
就緒狀態(tài)?
新建線程對象后,調(diào)用該線程的 start() 方法就可以啟動線程。當(dāng)線程啟動時(shí),線程進(jìn)入就緒狀態(tài)。此時(shí),線程將進(jìn)入線程隊(duì)列排隊(duì),等待 CPU 服務(wù),這表明它已經(jīng)具備了運(yùn)行條件。
-
運(yùn)行狀態(tài)?
當(dāng)就緒狀態(tài)被調(diào)用并獲得處理器資源時(shí),線程就進(jìn)入了運(yùn)行狀態(tài)。此時(shí),自動調(diào)用該線程對象的 run() 方法。run() 方法定義該線程的操作和功能。
-
阻塞狀態(tài)?
一個(gè)正在執(zhí)行的線程在某些特殊情況下,如被人為掛起或需要執(zhí)行耗時(shí)的輸入/輸出操作,會讓 CPU 暫時(shí)中止自己的執(zhí)行,進(jìn)入阻塞狀態(tài)。在可執(zhí)行狀態(tài)下,如果調(diào)用sleep(),suspend(),wait() 等方法,線程都將進(jìn)入阻塞狀態(tài),發(fā)生阻塞時(shí)線程不能進(jìn)入排隊(duì)隊(duì)列,只有當(dāng)引起阻塞的原因被消除后,線程才可以轉(zhuǎn)入就緒狀態(tài)。
-
死亡狀態(tài)?
線程調(diào)用 stop() 方法時(shí)或 run() 方法執(zhí)行結(jié)束后,即處于死亡狀態(tài)。處于死亡狀態(tài)的線程不具有繼續(xù)運(yùn)行的能力。
在此提出一個(gè)問題,Java 程序每次運(yùn)行至少啟動幾個(gè)線程?
回答:至少啟動兩個(gè)線程,每當(dāng)使用 Java 命令執(zhí)行一個(gè)類時(shí),實(shí)際上都會啟動一個(gè) JVM,每一個(gè)JVM實(shí)際上就是在操作系統(tǒng)中啟動一個(gè)線程,Java 本身具備了垃圾的收集機(jī)制。所以在 Java 運(yùn)行時(shí)至少會啟動兩個(gè)線程,一個(gè)是 main 線程,另外一個(gè)是垃圾收集線程。
取得和設(shè)置線程的名稱
class MyThread implements Runnable{ //實(shí)現(xiàn)Runnable接口public void run(){for(int i=0;i<3;i++){System.Out.Println(Thread.currentThread().getName()+"運(yùn)行, i="+i); //取得當(dāng)前線程的名稱}} };public class ThreadDemo{ public static void main(String args[]){MyThread my=new MyThread(); //定義Runnable子類對象new Thread(my).start; //系統(tǒng)自動設(shè)置線程名稱new Thread(my,"線程A").start(); //手工設(shè)置線程名稱} };程序運(yùn)行結(jié)果:?
四、線程的操作方法
剛才在分析自定義模式工作原理的時(shí)候其實(shí)就已經(jīng)提到了,如果想要更改Glide的默認(rèn)配
線程的強(qiáng)制運(yùn)行
在線程操作中,可以使用 join() 方法讓一個(gè)線程強(qiáng)制運(yùn)行,線程強(qiáng)制運(yùn)行期間,其他線程無法運(yùn)行,必須等待此線程完成之后才可以繼續(xù)執(zhí)行。
class MyThread implements Runnable{ // 實(shí)現(xiàn)Runnable接口public void run(){ // 覆寫run()方法for(int i=0;i<50;i++){System.out.println(Thread.currentThread().getName()+ "運(yùn)行,i = " + i) ; // 取得當(dāng)前線程的名字}} }; public class ThreadJoinDemo{public static void main(String args[]){MyThread mt = new MyThread() ; // 實(shí)例化Runnable子類對象Thread t = new Thread(mt,"線程"); // 實(shí)例化Thread對象t.start() ; // 啟動線程for(int i=0;i<50;i++){if(i>10){try{t.join() ; // 線程強(qiáng)制運(yùn)行}catch(InterruptedException e){}}System.out.println("Main線程運(yùn)行 --> " + i) ;}} };程序運(yùn)行結(jié)果:?
線程的休眠
在程序中允許一個(gè)線程進(jìn)行暫時(shí)的休眠,直接使用 Thread.sleep() 即可實(shí)現(xiàn)休眠。
class MyThread implements Runnable{ // 實(shí)現(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()+ "運(yùn)行,i = " + i) ; // 取得當(dāng)前線程的名字}} }; public class ThreadSleepDemo{public static void main(String args[]){MyThread mt = new MyThread() ; // 實(shí)例化Runnable子類對象Thread t = new Thread(mt,"線程"); // 實(shí)例化Thread對象t.start() ; // 啟動線程} };程序執(zhí)行結(jié)果:?
中斷線程
當(dāng)一個(gè)線程運(yùn)行時(shí),另外一個(gè)線程可以直接通過interrupt()方法中斷其運(yùn)行狀態(tài)。
class MyThread implements Runnable{ // 實(shí)現(xiàn)Runnable接口public void run(){ // 覆寫run()方法System.out.println("1、進(jìn)入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() ; // 實(shí)例化Runnable子類對象Thread t = new Thread(mt,"線程"); // 實(shí)例化Thread對象t.start() ; // 啟動線程try{Thread.sleep(2000) ; // 線程休眠2秒}catch(InterruptedException e){System.out.println("3、休眠被終止") ;}t.interrupt() ; // 中斷線程執(zhí)行} };程序運(yùn)行結(jié)果是:
后臺線程
在 Java 程序中,只要前臺有一個(gè)線程在運(yùn)行,則整個(gè) Java 進(jìn)程都不會消失,所以此時(shí)可以設(shè)置一個(gè)后臺線程,這樣即使 Java 線程結(jié)束了,此后臺線程依然會繼續(xù)執(zhí)行,要想實(shí)現(xiàn)這樣的操作,直接使用 setDaemon() 方法即可。
class MyThread implements Runnable{ // 實(shí)現(xiàn)Runnable接口public void run(){ // 覆寫run()方法while(true){System.out.println(Thread.currentThread().getName() + "在運(yùn)行。") ;}} }; public class ThreadDaemonDemo{public static void main(String args[]){MyThread mt = new MyThread() ; // 實(shí)例化Runnable子類對象Thread t = new Thread(mt,"線程"); // 實(shí)例化Thread對象t.setDaemon(true) ; // 此線程在后臺運(yùn)行t.start() ; // 啟動線程} };在線程類 MyThread 中,盡管 run() 方法中是死循環(huán)的方式,但是程序依然可以執(zhí)行完,因?yàn)榉椒ㄖ兴姥h(huán)的線程操作已經(jīng)設(shè)置成后臺運(yùn)行。
線程的優(yōu)先級
在 Java 的線程操作中,所有的線程在運(yùn)行前都會保持在就緒狀態(tài),那么此時(shí),哪個(gè)線程的優(yōu)先級高,哪個(gè)線程就有可能會先被執(zhí)行。
class MyThread implements Runnable{ // 實(shí)現(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()+ "運(yùn)行,i = " + i) ; // 取得當(dāng)前線程的名字}} }; public class ThreadPriorityDemo{public static void main(String args[]){Thread t1 = new Thread(new MyThread(),"線程A") ; // 實(shí)例化線程對象Thread t2 = new Thread(new MyThread(),"線程B") ; // 實(shí)例化線程對象Thread t3 = new Thread(new MyThread(),"線程C") ; // 實(shí)例化線程對象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() ; // 啟動線程} };程序運(yùn)行結(jié)果:?
從程序的運(yùn)行結(jié)果中可以觀察到,線程將根據(jù)其優(yōu)先級的大小來決定哪個(gè)線程會先運(yùn)行,但是需要注意并非優(yōu)先級越高就一定會先執(zhí)行,哪個(gè)線程先執(zhí)行將由 CPU 的調(diào)度決定。
線程的禮讓
在線程操作中,也可以使用 yield() 方法將一個(gè)線程的操作暫時(shí)讓給其他線程執(zhí)行
程序執(zhí)行結(jié)果:?
五、同步以及死鎖
一個(gè)多線程的程序如果是通過 Runnable 接口實(shí)現(xiàn)的,則意味著類中的屬性被多個(gè)線程共享,那么這樣就會造成一種問題,如果這多個(gè)線程要操作同一個(gè)資源時(shí)就有可能出現(xiàn)資源同步問題。
解決方法:
同步代碼塊
synchronized(同步對象){ 需要同步的代碼 } class MyThread implements Runnable{private int ticket = 5 ; // 假設(shè)一共有5張票public void run(){for(int i=0;i<100;i++){synchronized(this){ // 要對當(dāng)前對象進(jìn)行同步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() ;} };程序執(zhí)行結(jié)果:?
同步方法
除了可以將需要的代碼設(shè)置成同步代碼塊外,也可以使用 synchronized 關(guān)鍵字將一個(gè)方法聲明為同步方法。
synchronized 方法返回值 方法名稱(參數(shù)列表){ } class MyThread implements Runnable{private int ticket = 5 ; // 假設(shè)一共有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() ;} };程序執(zhí)行結(jié)果:?
從程序運(yùn)行的結(jié)果可以發(fā)現(xiàn),此代碼完成了與之前同步代碼同樣的功能。
死鎖
同步可以保證資源共享操作的正確性,但是過多同步也會產(chǎn)生問題。例如,現(xiàn)在張三想要李四的畫,李四想要張三的書,張三對李四說“把你的畫給我,我就給你書”,李四也對張三說“把你的書給我,我就給你畫”兩個(gè)人互相等對方先行動,就這么干等沒有結(jié)果,這實(shí)際上就是死鎖的概念。
所謂死鎖,就是兩個(gè)線程都在等待對方先完成,造成程序的停滯,一般程序的死鎖都是在程序運(yùn)行時(shí)出現(xiàn)的。
下面以一個(gè)簡單范例說明這個(gè)概念
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() ; // 實(shí)例化static型對象private static Lisi ls = new Lisi() ; // 實(shí)例化static型對象private boolean flag = false ; // 聲明標(biāo)志位,判斷那個(gè)先說話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() ;} };程序運(yùn)行結(jié)果:
以下代碼不再執(zhí)行,程序進(jìn)入死鎖狀態(tài)。
六、總結(jié)
至此關(guān)于多線程一些基本操作就介紹完了,鑒于筆者經(jīng)驗(yàn)有限,如果有什么不足和缺漏的地方,歡迎相互交流學(xué)習(xí),感謝大家!
總結(jié)
以上是生活随笔為你收集整理的Java并发—基础操作的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 重要接口—Serializable接口
- 下一篇: Java并发—锁的使用及原理