淺談java中的線程,多線程

卑微小吳勵志寫博客第三天

在這裏插入圖片描述

多線程

1 什麼是線程,線程與進程的關係

線程是程序的執行路徑,或者可以說是程序的控制單元。
一個進程可能包含一個或多個線程,當一個進程存在多條執行路徑時,就可以將該執行方式稱爲多線程。
線程的執行方式大致可分爲就緒(wait),執行(run),阻塞(block)三個狀態,而三個狀態的轉換實質上是在搶奪cpu資源過程中造成的,正常情況下cpu資源不會被線程獨自佔用,因此多個線程在運行中相互搶奪資源,造成線程在上述的三個狀態之間不斷的相互轉換。而這也是多線程的執行方式。

2 同步和異步的區別

同步(Sync)
所謂同步,就是發出一個功能調用時,在沒有得到結果之前,該調用就不返回或繼續執行後續操作。
根據這個定義,Java中所有方法都是同步調用,應爲必須要等到結果後纔會繼續執行。我們在說同步、異步的時候,一般而言是特指那些需要其他端協作或者需要一定時間完成的任務。
簡單來說,同步就是必須一件一件事做,等前一件做完了才能做下一件事。
異步(Async)
異步與同步相對,當一個異步過程調用發出後,調用者在沒有得到結果之前,就可以繼續執行後續操作。當這個調用完成後,一般通過狀態、通知和回調來通知調用者。對於異步調用,調用的返回並不受調用者控制。

舉個例子簡單說明下兩者的區別:
同步:火車站多個窗口賣火車票,假設A窗口當賣第288張時,在這個短暫的過程中,其他窗口都不能賣這張票,也不能繼續往下賣,必須這張票處理完其他窗口才能繼續賣票。直白點說就是當你看見程序裏出現synchronized這個關鍵字,將任務鎖起來,當某個線程進來時,不能讓其他線程繼續進來,那就代表是同步了。

異步:當我們用手機下載某個視頻時,我們大多數人都不會一直等着這個視頻下載完,而是在下載的過程看看手機裏的其他東西,比如用qq或者是微信聊聊天,這種的就是異步,你執行你的,我執行我的,互不干擾。比如上面賣火車票,如果多個窗口之間互不影響,我行我素,A窗口賣到第288張了,B窗口不管A窗口,自己也賣第288張票,那顯然會出錯了

3 創建線程的三種方式

  • 繼承Thread類,重寫父類run()方法
public class MyThread extends Thread{

    @Override
   public void run() {
   	// TODO Auto-generated method stub
   	//super.run();
   	 doSomething();
   }

   private void doSomething() {
   	// TODO Auto-generated method stub
   	System.out.println("我是一個線程中的方法");
   }
}
  • 實現runnable接口
public class RunnableThread implements Runnable{

   @Override
   public void run() {
   	// TODO Auto-generated method stub
   	doSomeThing();
   }

   private void doSomeThing() {
   	// TODO Auto-generated method stub
   	System.out.println("我是一個線程方法");
   }

}
  • 使用ExecutorService、Callable、Future實現有返回結果的多線程(JDK5.0以後)
public class CallableThread implements Callable<String>{
 
	@Override
	public String call() throws Exception {
		// TODO Auto-generated method stub
		doSomeThing();
		return "需要返回的值";
	}
 
	private void doSomeThing() {
		// TODO Auto-generated method stub
		System.out.println("我是線程中的方法");
	}
 
}

3.1 採用實現Runnable、Callable接口的方式創建多線程的優缺點。

優勢:

  • 線程類只是實現了Runnable接口與Callable接口,還可以繼承其他類。
  • 在這種方式下,多個線程可以共享一個target對象,所以非常適合多個相同線程來處理同一份資源的情況,從而可以將CPU、代碼和數據分開,形成清晰的模型,較好地體現了面向對象的思想。

劣勢:

  • 編程稍稍複雜,如果需要訪問當前線程,則必須使用Thread.currentThread()方法。

3.2 採用繼承Thread類的方法創建多線程的優缺點。

優勢:

  • 編寫簡單,如果需要訪問當前線程,則無須使用Thread.currentThread()方法,直接使用this即可獲得當前線程。

劣勢:

  • 因爲線程類已經繼承了Thread類,所以不能再繼承其他父類。

總結:一般推薦採用實現Runnable接口、Callable接口的方式來創建多線程。

4 線程池

4.1 線程池的作用

線程池作用就是限制系統中執行線程的數量。
根據系統的環境情況,可以自動或手動設置線程數量,達到運行的最佳效果;少了浪費了系統資源,多了造成系統擁擠效率不高。用線程池控制線程數量,其他線程 排隊等候。一個任務執行完畢,再從隊列的中取最前面的任務開始執行。若隊列中沒有等待進程,線程池的這一資源處於等待爲什麼要用線程池:。當一個新任務需要運行時,如果線程 池中有等待的工作線程,就可以開始運行了;否則進入等待隊列。

4.2爲什麼要用線程池

減少了創建和銷燬線程的次數,每個工作線程都可以被重複利用,可執行多個任務
可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因爲因爲消耗過多的內存,而把服務
器累趴下(每個線程需要大約 1MB 內存,線程開的越多,消耗的內存也就越大,最後死機)

4.3 java中的四種線程池

  • newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。
  • newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
  • newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
  • newSingleThreadExecutor
    創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

5 多線程同步機制。

  • 在需要同步的方法的方法簽名中加入synchronized關鍵字。
  • 使用synchronized塊對需要進行同步的代碼段進行同步。
  • 使用JDK 5中提供的java.util.concurrent.lock包中的Lock對象。
    一段synchronized的代碼被一個線程執行之前,他要先拿到執行這段代碼的權限,在 java裏邊就是拿到某個同步對象的鎖(一個對象只有一把鎖); 如果這個時候同步對象的鎖被其他線程拿走了,他(這個線程)就只能等了(線程阻塞在鎖池 等待隊列中)。 取到鎖後,他就開始執行同步代碼(被synchronized修飾的代碼);線程執行完同步代碼後馬上就把鎖還給同步對象,其他在鎖池中 等待的某個線程就可以拿到鎖執行同步代碼了。這樣就保證了同步代碼在統一時刻只有一個線程在執行。

6 線程的生命週期

線程在執行過程中,可以處於下面幾種狀態:
就緒(Runnable):線程準備運行,不一定立馬就能開始執行。
運行中(Running):進程正在執行線程的代碼。
等待中(Waiting):線程處於阻塞的狀態,等待外部的處理結束。
睡眠中(Sleeping):線程被強制睡眠。
I/O阻塞(Blocked on I/O):等待I/O操作完成。
同步阻塞(Blocked on Synchronization):等待獲取鎖。
死亡(Dead):線程完成了執行。
在這裏插入圖片描述

7 什麼是守護線程?

守護線程(即daemon thread),是個服務線程,準確地來說就是服務其他的線程,這是它的作用——而其他的線程只有一種,那就是用戶線程。所以java裏線程分2種,
1、守護線程,比如垃圾回收線程,就是最典型的守護線程。

守護線程,專門用於服務其他的線程,如果其他的線程(即用戶自定義線程)都執行完畢,連main線程也執行完畢,那麼jvm就會退出(即停止運行)——此時,連jvm都停止運行了,守護線程當然也就停止執行了。
再換一種說法,如果有用戶自定義線程存在的話,jvm就不會退出——此時,守護線程也不能退出,也就是它還要運行,幹嘛呢,就是爲了執行垃圾回收的任務啊。

2、用戶線程,就是應用程序裏的自定義線程。

應用程序裏的線程,一般都是用戶自定義線程。
用戶也可以在應用程序代碼自定義守護線程,只需要調用Thread類的設置方法設置一下即可

8 線程鎖

多線程可以同時運行多個任務
但是當多個線程同時訪問共享數據時,可能導致數據不同步,甚至錯誤!
so,不使用線程鎖, 可能導致錯誤。
應用場景:
I/O密集型操作 需要資源保持同步
優缺點:
優點:保證資源同步
缺點:有等待肯定會慢

9 sleep方法和wait方法的區別?

  • 來自不同的類: wait()方法是Object類的方法,sleep方法是Thread類的方法。
  • 對於鎖的佔用情況不同:最主要是sleep方法沒有釋放鎖,而 wait 方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。
  • 使用範圍: wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在任何地方使用)
  • 喚醒方式:調用sleep()方法的線程通常是睡眠一定時間後,自動醒來。對象調用wait()方法,必須採用notify()或者notifyAll()方法喚醒。

Sleep()方法

Sleep()方法屬於Thread類中方法,表示讓一個線程進入睡眠狀態,等待一定的時間之後,自動醒來進入到可運行狀態,不會馬上進入運行狀態,因爲線程調度機制恢復線程的運行也需要時間,一個線程對象調用了sleep方法之後,並不會釋放他所持有的所有對象鎖,所以也就不會影響其他進程對象的運行。但在sleep的過程中有可能被其他對象調用它的interrupt(),產生InterruptedException異常,如果你的程序不捕獲這個異常,線程就會異常終止,進入TERMINATED狀態,如果你的程序捕獲了這個異常,那麼程序就會繼續執行catch語句塊(可能還有finally語句塊)以及以後的代碼。
注意sleep()方法是一個靜態方法,也就是說他只對當前對象有效,通過t.sleep()讓t對象進入sleep,這樣的做法是錯誤的,它只會是使當前線程被sleep 而不是t線程。

Wait()方法

Wait()屬於Object的成員方法,一旦一個對象調用了wait方法,必須要採用notify()和notifyAll()方法喚醒該進程;如果線程擁有某個或某些對象的同步鎖,那麼在調用了wait()後,這個線程就會釋放它持有的所有同步資源,而不限於這個被調用了wait()方法的對象。wait()方法也同樣會在wait的過程中有可能被其他對象調用interrupt()方法而產生InterruptedException,效果以及處理方式同sleep()方法。

10 什麼是死鎖(deadlock)?如何避免死鎖?

兩個進程都在等待對方執行完畢才能繼續往下執行的時候就發生了死鎖。結果就是兩個進程都陷入了無限的等待中。
使用多線程的時候,一種非常簡單的避免死鎖的方式就是:指定獲取鎖的順序,並強制線程按照指定的順序獲取鎖。因此,如果所有的線程都是以同樣的順序加鎖和釋放鎖,就不會出現死鎖了。

關於多線程與鎖的一些知識,確實很難。每次看到這裏都沒弄的太懂,不過多看一些關於多線程的知識後還是有一些進步的。希望這篇博客能幫助小夥伴對線程,多線程有一定的瞭解。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章