【Java學習筆記九】多線程

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

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

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

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

觀察以下程序:

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

代碼分析:

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

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

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

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

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

創建線程

  1. 繼承java.lang.Thread()類,覆蓋run()方法。在創建的Thread類的子類中重寫run(),加入線程所要執行的代碼。
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");} 
} 	

運行結果:(兩次完全相同的運行,但是結果卻不同,說明線程大概是同步進行的,而且順序是隨機的)
在這里插入圖片描述在這里插入圖片描述

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

例如:

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);	//創建線程Thread f2=new Thread(ft2);Thread f3=new Thread(ft3);f1.start(); f2.start();f3.start();}
}

運行對象:
在這里插入圖片描述

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

線程的啟動

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

線程的實現

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

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

簡單來講:

  1. 創建狀態:使用new運算符創建一個線程。
  2. 可運行狀態:使用start()方法啟動一個線程后,系統分配了資源。
  3. 運行中狀態:執行線程的run()方法。
  4. 阻塞狀態:運行的線程因某種原因停止繼續運行。
  5. 死亡狀態:線程結束。

線程的調度

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

多線程的互斥和同步

線程的同步

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

同步的原理

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

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

同步的實現

在Java中通過互斥鎖標志Synchronized關鍵字的運用來實現同步。Java中同步有兩種方法:

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

例子:

class TicketRunnable implements Runnable 
{ private int ticketNum = 3; //  以3 張票為例 public void run() { while (true) { String tName = Thread.currentThread().getName(); //  將需要獨占 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(); } 
} 

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

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

死鎖

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

  1. 互斥條件。線程中使用的資源中至少要有一個是不能共享的。
  2. 至少有一個線程它必須持有一個資源且正在等待獲取一個當前被別的線程持有的資源。
  3. 資源不能被線程搶占。
  4. 必須有循環等待,這時,一個線程等待其他線程所持有的資源,后者又在等待另一個線程所持有的資源。

死鎖的實例:(待更,腦袋不轉了)

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383800.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383800.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383800.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【C++11新特性】 C++11智能指針之weak_ptr

http://blog.csdn.net/xiejingfa/article/details/50772571 原創作品&#xff0c;轉載請標明&#xff1a;http://blog.csdn.net/Xiejingfa/article/details/50772571 如題&#xff0c;我們今天要講的是C11引入的三種智能指針中的最后一個&#xff1a;weak_ptr。在學習weak_ptr之…

【C++學習筆記四】運算符重載

當調用一個重載函數和重載運算符時&#xff0c;編譯器通過把您所使用的參數類型和定義中的參數類型相比較&#xff0c;巨鼎選用最合適的定義。&#xff08;重載決策&#xff09; 重載運算符時帶有特殊名稱的函數&#xff0c;函數名是由關鍵字operator和其后要重載的運算符符號…

【C++11新特性】 C++11智能指針之unique_ptr

原創作品&#xff0c;轉載請標明&#xff1a;http://blog.csdn.net/Xiejingfa/article/details/50759210 在前面一篇文章中&#xff0c;我們了解了C11中引入的智能指針之一shared_ptr&#xff0c;今天&#xff0c;我們來介紹一下另一種智能指針unique_ptr。 unique_ptr介紹 uni…

C++派生類對象和基類對象賦值

在C中&#xff0c;我們允許 將派生類對象賦給基類對象。&#xff08;不允許將基類對象賦給派生類對象&#xff09; 只會將基類對象成員賦值用基類指針指向派生類對象。&#xff08;不允許用派生類指針指向基類對象&#xff09; 基類指針只能操作基類中的成員基類引用作為派生類…

【C++11新特性】 C++11智能指針之shared_ptr

http://blog.csdn.net/Xiejingfa/article/details/50750037 原創作品&#xff0c;轉載請標明&#xff1a;http://blog.csdn.net/Xiejingfa/article/details/50750037 C中的智能指針首先出現在“準”標準庫boost中。隨著使用的人越來越多&#xff0c;為了讓開發人員更方便、更安…

C++(純)虛函數重寫時訪問權限更改問題

我們知道在Java中是自動實現多態的&#xff0c;Java中規定重寫的方法的訪問權限不能縮小。那么在C中我們實現多態的時候是否可以更改&#xff08;縮小&#xff09;訪問權限呢&#xff1f; 經過測試&#xff0c;得到的答案如下&#xff1a;如果用基類指針指向派生類對象實現多態…

C++ — 智能指針的簡單實現以及循環引用問題

http://blog.csdn.net/dawn_sf/article/details/70168930 智能指針 ____________________________________________________ 今天我們來看一個高大上的東西&#xff0c;它叫智能指針。 哇這個名字聽起來都智能的不得了&#xff0c;其實等你了解它你一定會有一點失望的。。。。因…

C++(靜態)(常量)數據進行初始化問題以及靜態變量析構

在C11標準以前我們都不可以在類中對數據成員初始化&#xff0c;僅能在構造函數中進行初始化&#xff1a; class A {int a,b; double c; string d;A():a(1),b(2),c(3),d(""){} };在C11標準以后我們可以在類中對非靜態成員進行初始化。實際上的機制是在調用構造函數的…

C++this指針的用法

參考博客&#xff1a;https://www.cnblogs.com/zhengfa-af/p/8082959.html 在 訪問對象的非靜態成員時會隱式傳遞一個參數&#xff0c;即對象本身的指針&#xff0c;這個指針名為this。 例如&#xff1a; class A {int a1;public:A(){}void GetA(int a){cout<<this-&g…

C++開發者都應該使用的10個C++11特性

http://blog.jobbole.com/44015/ 感謝馮上&#xff08;治不好你我就不是獸醫 &#xff09;的熱心翻譯。如果其他朋友也有不錯的原創或譯文&#xff0c;可以嘗試推薦給伯樂在線。】 在C11新標準中&#xff0c;語言本身和標準庫都增加了很多新內容&#xff0c;本文只涉及了一些皮…

C++不能被聲明為虛函數

虛函數是為了實現多態&#xff0c;但是顯然并不是所有函數都可以聲明為虛函數的。 不能被聲明為虛函數的函數有兩類&#xff1a; 不能被繼承的函數不能被重寫的函數 因此&#xff0c;這些函數都不能被聲明為虛函數 普通函數構造函數 如果構造函數定義為虛函數&#xff0c;則…

類的聲明與定義

類的前向聲明&#xff1a; class A;在聲明之后&#xff0c;定義之前&#xff0c;類A是一個不完全類型&#xff0c;即知道A是一個類&#xff0c;但是不知道包含哪些成員。不完全類型只能以有限方式使用&#xff0c;不能定義該類型的對象&#xff0c;不完全類型只能用于定義指向…

shared_ptr的一些尷尬

http://blog.csdn.net/henan_lujun/article/details/8984543 shared_ptr在boost庫中已經有多年了&#xff0c;C11又為其正名&#xff0c;把他引入了STL庫&#xff0c;放到了std的下面&#xff0c;可見其頗有用武之地&#xff1b;但是shared_ptr是萬能的嗎&#xff1f;有沒有什…

C++轉換構造函數和類型轉換函數

參考博客&#xff1a;https://blog.csdn.net/feiyanaffection/article/details/79183340 隱式類型轉換 如果不同類型的數據在一起操作的時候編譯器會自動進行一個數據類型轉換。例如常用的基本數據類型有如下類型轉換關系&#xff1a; 轉換構造函數 構造函數有且僅有一個參數…

C++總結8——shared_ptr和weak_ptr智能指針

http://blog.csdn.net/wendy_keeping/article/details/75268687 智能指針的提出&#xff1a;智能指針是存儲指向動態分配對象指針的類&#xff0c;用于生存期控制。能夠確保正確銷毀動態分配的內存&#xff0c;防止內存泄露。 1.智能指針的分類&#xff1a; 不帶引用計數的智能…

C++析構函數執行順序

今天發現主程序中有多個對象時析構函數的執行順序不是對象定義的順序&#xff0c;而是對象定義順序反過來。 思考了一下&#xff0c;結合之前繼承、成員對象等的析構函數執行的順序&#xff0c;我覺得析構函數執行的順序為&#xff1a;構造函數的順序反過來&#xff0c;可能是…

c++寫時拷貝1

http://blog.csdn.net/SuLiJuan66/article/details/48882303 Copy On Write Copy On Write(寫時復制)使用了“引用計數”&#xff08;reference counting&#xff09;&#xff0c;會有一個變量用于保存引用的數量。當第一個類構造時&#xff0c;string的構造函數會根據傳入的參…

【C++學習筆記五】模板

模板是泛型編程的基礎 函數模板 模板定義以關鍵字template開始&#xff0c;后跟一個模板參數列表。這是一個逗號分隔的一個或多個模板參數的列表。用尖括號包圍起來。 模板函數定義的一般形式&#xff1a; template <class type> ret-tye func-name(parameter list) …

【Java學習筆記十】輸入輸出流

在Java.io包中提供了一系列用于處理輸入/輸出的流類。從功能上分為兩類&#xff1a;輸入流和輸出流。從六結構上可分為&#xff1a;字節流&#xff08;以字節為處理單位&#xff09;和字符流&#xff08;以字符為處理單位&#xff09;。 字符是由字節組成。在Java中所有字符用…

C++ 寫時拷貝 2

什么情況下會用到c中的拷貝構造函數】&#xff1a; 1&#xff09;用已經存在的同類的對象去構造出另一個新的對象 2&#xff09;當函數的形參是類的對象時&#xff0c;這時調用此函數&#xff0c;使用的是值的拷貝&#xff0c;也會調用拷貝構造函數 3&#xff09;當函數的返…