日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

【Java学习笔记九】多线程

發(fā)布時間:2023/11/30 java 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Java学习笔记九】多线程 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

程序:計(jì)算機(jī)指令的集合,它以文件的形式存儲在磁盤上,是應(yīng)用程序執(zhí)行的藍(lán)本。
進(jìn)程:是一個程序在其自身的地址空間中的一次執(zhí)行活動。進(jìn)程是資源申請、調(diào)度和獨(dú)立運(yùn)行的單位,因此,它使用系統(tǒng)中的運(yùn)行資源。而程序不能申請系統(tǒng)資源,不能被系統(tǒng)調(diào)度,也不能作為獨(dú)立運(yùn)行的單位,它不占用系統(tǒng)的運(yùn)行資源。作為藍(lán)本的程序可以被多次加載到系統(tǒng)的不同內(nèi)存區(qū)域分別執(zhí)行,形成不同的進(jìn)程。基于進(jìn)程的特點(diǎn)是允許計(jì)算機(jī)同時運(yùn)行兩個或更多的程序。
線程:是進(jìn)程中的一個單一的順序控制流程,一個進(jìn)程在執(zhí)行過程中,可以產(chǎn)生多個線程。每個線程也有自己產(chǎn)生、存在和消亡的過程。
線程又稱為輕量級進(jìn)程,它和進(jìn)程一樣擁有獨(dú)立的執(zhí)行控制,由操作系統(tǒng)負(fù)責(zé)調(diào)度,區(qū)別在于線程沒有獨(dú)立的存儲空間,而是和所屬進(jìn)程中的其它線程共享一個存儲空間。這使得線程間的通信遠(yuǎn)較進(jìn)程簡單,而進(jìn)程之間的通信則比較困難,另外在資源的占用上,線程比進(jìn)程要小。

線程(Thread)和進(jìn)程(Process)的關(guān)系很密切,進(jìn)程和線程是兩個不同的概念,進(jìn)程的范圍大于線程

通俗的講,進(jìn)程就是一個程序,線程是這個程序能夠同時做的各件事情。例如:媒體播放器運(yùn)行時就是一個進(jìn)程,而媒體播放器同時下載文件和播放歌曲就是兩個線程。
從另一個角度講,每個進(jìn)程都擁有一組完整的屬于自己的變量,而線程則共享一個進(jìn)程中的這些數(shù)據(jù)。

主線程:程序啟動時,一個線程立即運(yùn)行,該線程稱為程序的主線程。(main()方法),其他線程都是由主線程產(chǎn)生的,主線程通常必須最后完成執(zhí)行,因此需要執(zhí)行各種關(guān)閉動作。

觀察以下程序:

public class MainThreadDemo {public static void main(String args[]) {Thread t = Thread.currentThread();System.out.println("當(dāng)前線程名稱是: " + t.getName());t.setName("MyJavaThread");System.out.println("改名后線程名稱是: " + t.getName());System.out.println("輸出當(dāng)前線程: " + t); } }

代碼分析:

  • Thread.currentThread()是一個靜態(tài)方法,返回正在執(zhí)行的線程對象的引用。
  • getName()方法可以得到當(dāng)前引用線程的名稱
  • setName(String s)可以改變線程的內(nèi)部名稱
  • 當(dāng)將一個線程對象作為輸出時,將輸出[線程名稱,優(yōu)先級別,線程組名 ]
  • 每個線程都屬于一個線程組,如果沒有設(shè)定,則由JVM來設(shè)定。

并發(fā)編程:在計(jì)算機(jī)編程中有一個基本概念,就是在同一時刻處理多個任務(wù)的思想。許多程序設(shè)計(jì)問題都要求程序能夠停下正在做的工作,轉(zhuǎn)而處理某個其他問題,然后再返回主進(jìn)程。多線程的任務(wù)相比傳統(tǒng)的進(jìn)程而言(只有一個主線程),可以同時有多個地方執(zhí)行代碼。

把問題切分成多個可獨(dú)立運(yùn)行的部分(任務(wù)),從而提高程序的響應(yīng)能力。在程序中,這些彼此獨(dú)立運(yùn)行的部分稱之為線程,上述概念被稱為“并發(fā)”。

Java提供了類 java.lang.Thread 來方便多線程編程,這個類提供了大量的方法方便控制線程.

Thread類最重要的方法是run(),它為Thread類的方法start()所調(diào)用,為了指定我們自己的代碼,需要覆蓋run()方法,來提供我們線程所要執(zhí)行的代碼。

創(chuàng)建線程

  • 繼承java.lang.Thread()類,覆蓋run()方法。在創(chuàng)建的Thread類的子類中重寫run(),加入線程所要執(zhí)行的代碼。
  • class mythread extends Thread {public void run(){} }

    例如:

    package Test;class FileTransThread extends Thread{ private String fileName; public FileTransThread(String fileName){ this.fileName = fileName; } public void run(){ System.out.println("傳送" + fileName); try{ Thread.sleep(1000 * 10); }catch(Exception ex){} System.out.println(fileName + "傳送完畢"); } } public class Main { public static void main(String[] args) throws Exception { FileTransThread ft1 = new FileTransThread("文件1"); FileTransThread ft2 = new FileTransThread("文件2"); FileTransThread ft3 = new FileTransThread("文件3"); ft1.start(); System.out.println("1");System.out.println("2");ft2.start();System.out.println("3");System.out.println("4");ft3.start(); System.out.println("5");System.out.println("6");} }

    運(yùn)行結(jié)果:(兩次完全相同的運(yùn)行,但是結(jié)果卻不同,說明線程大概是同步進(jìn)行的,而且順序是隨機(jī)的)

  • 繼承java.lang.Thread()類方法簡單明了,符合大家的習(xí)慣。可是如果這個類已經(jīng)繼承了一個類,則沒有辦法再繼承java.lang.Thread()類。這時我們可以實(shí)現(xiàn)java.lang.Runnable接口,并實(shí)現(xiàn)run()方法。然后通過這個類創(chuàng)建線程。詳見實(shí)例:
  • class mythread implements Runnable {public void run( ) {/* 實(shí)現(xiàn)該方法*/ } }

    例如:

    package Test;class FileTransRunnable implements Runnable { private String fileName; public FileTransRunnable(String fileName){ this.fileName = fileName; }public void run(){ System.out.println("傳送" + fileName); try{ Thread.sleep(1000 * 10); }catch(Exception ex){} System.out.println(fileName + "傳送完畢"); } } public class Main { public static void main(String[] args) throws Exception { FileTransRunnable ft1 = new FileTransRunnable("文件1"); FileTransRunnable ft2 = new FileTransRunnable("文件2"); FileTransRunnable ft3 = new FileTransRunnable("文件3");Thread f1=new Thread(ft1); //創(chuàng)建線程Thread f2=new Thread(ft2);Thread f3=new Thread(ft3);f1.start(); f2.start();f3.start();} }

    運(yùn)行對象:

    • 對象可以自由地繼承自另一個類。
    • 同一個runnable對象可以傳遞給多個線程,可以實(shí)現(xiàn)資源共享。

    線程的啟動

    新建的線程不會自動開始運(yùn)行,必須通過strat()方法啟動線程。如果不調(diào)用這個方法,線程將不會運(yùn)行。

    線程的實(shí)現(xiàn)

    線程從創(chuàng)建、啟動到終止的整個過程叫做一個生命周期。線程總共由五個狀態(tài)。

  • 新建狀態(tài)(new):創(chuàng)建之后還沒有調(diào)用start()。
  • 就緒狀態(tài)(Runnable):線程對象創(chuàng)建后,其他線程調(diào)用了該對象的start()方法,處于該狀態(tài)的線程位于可運(yùn)行線程池中,成為可運(yùn)行,等待獲取CPU的使用權(quán)。線程有資格運(yùn)行但調(diào)度程序還沒有把他選定為運(yùn)行線程時線程所處的狀態(tài)。當(dāng)start()方法調(diào)用時,線程首先進(jìn)入可運(yùn)行狀態(tài)。在線程運(yùn)行之后或者從阻塞、等待或睡眠狀態(tài)回來后,也返回到可運(yùn)行狀態(tài)。
  • 運(yùn)行狀態(tài)(Running):線程調(diào)度程序從可運(yùn)行池中選擇一個線程作為當(dāng)前線程時線程所處的狀態(tài),這也是線程進(jìn)入運(yùn)行狀態(tài)的唯一一種方式。就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼。
  • 阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因?yàn)槟撤N原因放棄CPU使用權(quán),暫時停止運(yùn)行。直到線程進(jìn)入就緒狀態(tài),才有機(jī)會轉(zhuǎn)到運(yùn)行狀態(tài)。阻塞的情況分三種:
    • 等待阻塞:運(yùn)行的線程執(zhí)行wait()方法,該線程放入等待池中。
    • 同步阻塞:運(yùn)行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程占用,該線程放入鎖池中。
    • 其他阻塞:運(yùn)行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請求時,該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉(zhuǎn)入就緒狀態(tài)。
  • 死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。這個線程對象也許還存在,但是,它已經(jīng)不是一個單獨(dú)執(zhí)行的線程。線程一旦死亡,就不能復(fù)生。 如果在一個死去的線程上調(diào)用start()方法,會拋出java.lang.IllegalThreadStateException異常。
  • 簡單來講:

  • 創(chuàng)建狀態(tài):使用new運(yùn)算符創(chuàng)建一個線程。
  • 可運(yùn)行狀態(tài):使用start()方法啟動一個線程后,系統(tǒng)分配了資源。
  • 運(yùn)行中狀態(tài):執(zhí)行線程的run()方法。
  • 阻塞狀態(tài):運(yùn)行的線程因某種原因停止繼續(xù)運(yùn)行。
  • 死亡狀態(tài):線程結(jié)束。
  • 線程的調(diào)度

  • 線程睡眠:public static void sleep(long millis) throws InterruptedException
    millis參數(shù)設(shè)定睡眠的時間,以毫秒為單位。當(dāng)睡眠結(jié)束后,就轉(zhuǎn)為就緒(Runnable)狀態(tài)。sleep()方法的平臺移植性較好。
  • 線程等待:Object類中的wait()方法,導(dǎo)致當(dāng)前的線程等待,直到其他線程調(diào)用此對象的 notify() 方法或 notifyAll() 喚醒方法。這個兩個喚醒方法是Object類中的方法,行為等價于調(diào)用 wait(0) 一樣。
  • 線程讓步:public static void yield() 方法,暫停當(dāng)前正在執(zhí)行的線程對象,把執(zhí)行機(jī)會讓給相同或者更高優(yōu)先級的線程。通過yield()方法,當(dāng)前線程把cpu讓給別的線程,而不用進(jìn)入休眠狀態(tài)而等待很長時間。該方法只影響當(dāng)前正在運(yùn)行的線程,且沒有任何機(jī)制保證它將會被采納。
  • 線程加入:public final void join() throws InterruptedException 方法,在當(dāng)前線程中調(diào)用另一個線程的join()方法,則當(dāng)前線程轉(zhuǎn)入阻塞狀態(tài),直到另一個進(jìn)程運(yùn)行結(jié)束,當(dāng)前線程再由阻塞轉(zhuǎn)為就緒狀態(tài)。
    另外,join()方法還有帶超時限制的重載版本。 例如t.join(5000);則讓線程等待5000毫秒,如果超過這個時間,則停止等待,變?yōu)榭蛇\(yùn)行狀態(tài)。
  • 線程喚醒:Object類中的notify()方法,喚醒在此對象監(jiān)視器上等待的單個線程。如果所有線程都在此對象上等待,則會選擇喚醒其中一個線程。選擇是任意性的。類似的方法還有一個notifyAll(),喚醒在此對象監(jiān)視器上等待的所有線程。
  • 多線程的互斥和同步

    線程的同步

    在多線程的程序中,多個線程可能會對同一個資源并發(fā)訪問。這種情況下,如果不對共享的資源進(jìn)行保護(hù),就可能產(chǎn)生問題。
    所謂同步(synchronize),就是在發(fā)出一個功能調(diào)用時,在沒有得到結(jié)果之前,該調(diào)用就不返回,同時其它線程也不能調(diào)用這個方法。
    通俗地講,一個線程是否能夠搶占CPU,必須考慮另一個線程中的某種條件,而不能隨便讓操作系統(tǒng)按照默認(rèn)方式分配CPU,如果條件不具備,就應(yīng)該等待另一個線程運(yùn)行,直到條件具備。

    同步的原理

    Java中每個對象都有一個內(nèi)置鎖,當(dāng)程序運(yùn)行到非靜態(tài)的synchronized同步方法上時,自動獲得與正在執(zhí)行代碼類的當(dāng)前實(shí)例(this實(shí)例)有關(guān)的鎖。獲得一個對象的鎖也稱為獲取鎖、鎖定對象、在對象上鎖定或在對象上同步。

    一個對象只有一個鎖。所以,如果一個線程獲得該鎖,就沒有其他線程可以獲得鎖,直到第一個線程釋放(或返回)鎖。這也意味著任何其他線程都不能進(jìn)入該對象上的synchronized方法或代碼塊,直到該鎖被釋放。釋放鎖是指持鎖線程退出了synchronized同步方法或代碼塊。

    同步的實(shí)現(xiàn)

    在Java中通過互斥鎖標(biāo)志Synchronized關(guān)鍵字的運(yùn)用來實(shí)現(xiàn)同步。Java中同步有兩種方法:

  • 方法級同步
  • synchronized void method( ) { //同步的方法}
    • 實(shí)現(xiàn)方法:在要標(biāo)志為同步的方法前加上synchronized關(guān)鍵字。如:public synchronized void call(String msg){ }
    • 實(shí)現(xiàn)原理:當(dāng)調(diào)用對象的同步方法時,線程取得對象鎖或監(jiān)視器,如果另一個線程試圖執(zhí)行同步方法,他就會發(fā)現(xiàn)被鎖住了,就會進(jìn)入掛起狀態(tài),直到對象監(jiān)視器上的鎖被釋放為止。當(dāng)鎖住放啊的線程從方法中返回時,只有一個排隊(duì)等候的線程可以訪問對象。
    • 鎖的作用域:該方法被執(zhí)行的整個時間。
  • 程序塊級同步
  • synchronized()object {}
    • 臨界區(qū):只希望防止多個線程同時訪問方法內(nèi)部的部分代碼,而不是防止訪問整個方法,通過這種方式分離出來的代碼段被稱為“臨界區(qū)”,即需要進(jìn)行互斥的代碼段。
    • 實(shí)現(xiàn)方法:用synchronized來指定某個對象,此對象的鎖被用來對花括號內(nèi)的代碼進(jìn)行同步控制。如:
    synchronized(target){target.call(msg);}
    • 實(shí)現(xiàn)原理:在進(jìn)入同步代碼前,必須得到object對象的鎖,如果其他線程已經(jīng)得到這個鎖,那么就得等到鎖被釋放后才能進(jìn)入臨界區(qū)。
    • 鎖的作用域:只在代碼塊運(yùn)行的時間內(nèi)

    例子:

    class TicketRunnable implements Runnable { private int ticketNum = 3; // 以3 張票為例 public void run() { while (true) { String tName = Thread.currentThread().getName(); // 將需要獨(dú)占 CPU的代碼用 synchronized(this)包圍起來 synchronized (this) { if (ticketNum <= 0) { System.out.println(tName + "無票"); break; } else { try { Thread.sleep(1000);// 程序休眠 1000 毫秒 }catch (Exception ex) {} ticketNum--; // 代碼行1 System.out.println(tName + "賣出一張票,還剩" + ticketNum + "張票'); } } } } } public class Main { public static void main(String[] args){ TicketRunnable tr = new TicketRunnable(); Thread th1 = new Thread(tr, "thread 1"); Thread th2 = new Thread(tr, "thread 2"); th1.start(); th2.start(); } }

    從以上代碼可以看出,該方法的本質(zhì)是將需要獨(dú)占 CPU 的代碼用synchronized(this)包圍起來。如前所述,一個線程進(jìn)入這段代碼之后,就在 this 上加了一個標(biāo)記,直到該線程將這段代碼運(yùn)行完畢,才釋放這個標(biāo)記。如果其他線程想要搶占 CPU,先要檢查 this 上是否有這個標(biāo)記。若有,就必須等待。

    但是可以看出,該代碼實(shí)際上運(yùn)行較慢,因?yàn)橐粋€線程的運(yùn)行,必須等待另一個線程將同步代碼段運(yùn)行完畢。因此,從性能上講,線程同步是非常耗費(fèi)資源的一種操作。我們要盡量控制線程同步的代碼段范圍,理論上說,同步的代碼段范圍越小,段數(shù)越少越好,因此在某些情況下,推薦將小的同步代碼段合并為大的同步代碼段。

    死鎖

    如果出現(xiàn)一種極端情況,一個線程等候另一個對象,而另一個對象又在等候下一個對象,以此類推。這個“等候鏈”如果進(jìn)入封閉狀態(tài),也就是說,最后那個對象等候的是第一個對象,此時,所有線程都會陷入無休止的相互等待狀態(tài),造成死鎖。盡管這種情況并非經(jīng)常出現(xiàn),但一旦碰到,程序的調(diào)試將變得異常艱難。
    發(fā)生死鎖必須同時滿足的四個條件:

  • 互斥條件。線程中使用的資源中至少要有一個是不能共享的。
  • 至少有一個線程它必須持有一個資源且正在等待獲取一個當(dāng)前被別的線程持有的資源。
  • 資源不能被線程搶占。
  • 必須有循環(huán)等待,這時,一個線程等待其他線程所持有的資源,后者又在等待另一個線程所持有的資源。
  • 死鎖的實(shí)例:(待更,腦袋不轉(zhuǎn)了)

    總結(jié)

    以上是生活随笔為你收集整理的【Java学习笔记九】多线程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。