java多線程并發(fā)(一)

  1. 為什么要并發(fā)編程?
  • 多線程并發(fā)處理會提升性能,為了讓程序運行的更快。但是,并不是啟動更多的線程能讓程序最大限度的并發(fā)執(zhí)行。
  1. 并發(fā)編程會面臨什么樣的問題?
  • 并發(fā)編程會面臨非常多的挑戰(zhàn),比如:上下文切換問題、死鎖問題、以及受限于硬件和軟件的資源限制問題。

2.1 什么是上下文切換?
- CPU通過時間片分配算法來循環(huán)執(zhí)行任務(wù),當前任務(wù)執(zhí)行一個時間片后會切換到下一個任務(wù)。但是,在切換前會保存上一個任務(wù)的狀態(tài),以便下次切換回這個任務(wù)時,可以再加載這個任務(wù)的狀態(tài)。所以任務(wù)從保存到再加載的過程就是一次上下文切換。
上下文切換會帶來什么樣的影響?

  • 上下文的切換會影響多線程的執(zhí)行速度

    如何減少上下文切換?

  • 減少上下文切換的方法:無鎖并發(fā)編程、CAS算法、使用最少線程和使用協(xié)程。

    無鎖并發(fā)編程:多線程競爭鎖時,會引起上下文切換,可以用一些方法來避免使用鎖。
    如:將數(shù)據(jù)的ID按照Hash算法取模分段,不同的線程處理不同的數(shù)據(jù)。

    CAS算法:java的Atomic包使用CAS算法來更新數(shù)據(jù),不需加鎖。

    使用最少線程:避免創(chuàng)建不必要的線程,這樣就不會造成大量線程處于等待狀態(tài)。

    協(xié)成:在單線程里實現(xiàn)多任務(wù)的調(diào)度,并在單線程里維持多個任務(wù)間的切換。

2.2 什么是死鎖?

  • 所謂死鎖是指多個進程循環(huán)等待它方占有的資源,而無限期地僵持不去的局面。

    死鎖會帶來什么樣的影響?

  • 一旦產(chǎn)生死鎖,就會造成系統(tǒng)功能不可用。

    避免死鎖常用的方法:

    避免一個線程同時獲得多個鎖。 避免一個線程在鎖內(nèi)同時占用多個資源,盡量保證每個鎖只占用一個資源。
    嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內(nèi)部鎖機制。
    對于數(shù)據(jù)庫鎖,加鎖和解鎖必須在一個數(shù)據(jù)庫連接里,否則會出現(xiàn)解鎖失敗的情況。

    2.3 什么是資源限制?

  • 在并發(fā)編程時,程序的執(zhí)行速度受限于計算機硬件資源或者軟件資源。
    硬件資源限制有帶寬的上傳和下載速度、硬件讀寫速度和CPU的處理速度。
    軟件資源限制有數(shù)據(jù)庫的連接數(shù)和Socket連接數(shù)等。

    資源限制引發(fā)的問題?

  • 并發(fā)編程的原則是將代碼中串行執(zhí)行的部分變成并發(fā)執(zhí)行,但是如果將某段串行的代碼并發(fā)執(zhí)行時,因為受限于資源,仍然在串行執(zhí)行,程序不僅不會加快,反而會更慢。

    如何解決資源限制問題?

    • 對于硬件資源限制,如使用集群并行執(zhí)行程序。例如使用ODPS、Hadoop或者自己搭建服務(wù)器集群,不同的機器處理不同的數(shù)據(jù)。

    • 對于軟件資源受限,如使用資源池將資源復(fù)用。例如使用連接池將數(shù)據(jù)庫和Socket連接復(fù)用,或者在調(diào)用對方webservice接口獲取數(shù)據(jù)時,建立一個連接。

3.什么是線程:

  • 線程是現(xiàn)代操作系統(tǒng)調(diào)度的最小單位,又叫輕量級進程,在一個進程里可以創(chuàng)建多個線程,這些線程擁有各自的計數(shù)器、堆棧和局部變量等屬性,并且能夠訪問共享的內(nèi)存變量。處理器在這些線程上高速切換,讓使用者感覺這些線程在同時執(zhí)行。而一個java程序的運行不僅僅總是main()方法的運行,而是main線程和多個其他線程同時運行。
**為什么要使用多線程?**

> 1.隨著處理器上的核心數(shù)量越來越多,現(xiàn)在大多數(shù)計算機都比以往更加擅長并行計算,如果程序使用多線程技術(shù),將計算邏輯分配到多個處理器核心上,就會顯著減少程序的處理時間,并且隨著處理器核心的加入而變得更有效。

2.響應(yīng)用戶請求的線程能夠盡可能快地處理完成,縮短了響應(yīng)時間,提升了用戶體驗。

**創(chuàng)建線程的目的:**
  • 為了開啟一條執(zhí)行路徑,去運行指定的代碼和其他代碼實現(xiàn)并行運行。開啟線程是為了運行指定代碼,所以必須要有Thread類對象或是其子類對象。

4 創(chuàng)建線程的兩種傳統(tǒng)方式:

  • (繼承Thread類,復(fù)寫run()方法;單獨實現(xiàn)Runnable接口然后把其對象傳入Thread構(gòu)造器,這兩種方法都必須調(diào)用Start來開啟線程)

    4.1:創(chuàng)建Thread的子類,覆蓋其中的run方法,運行這個子類的start方法即可開啟線程

public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {   //重寫run()方法
while (true) {
try {
Thread.sleep(500);  //線程休眠500毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("1" +Thread.currentThread().getName());  //得到當前線程
System.out.println("2" +Thread.currentThread().getName());
}
};
thread.start();  //啟動線程
*4.2  創(chuàng)建Thread時傳遞一個實現(xiàn)Runnable接口的對象實例*
public static void main(String[] args) {
Thread thread2 = new Thread(new Runnable() { //傳入一個Runnable接口的實例
@Override
public void run() {  //重寫run方法
while (true) {
try {
Thread.sleep(500);   //線程休眠500毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("4" +Thread.currentThread().getName()); //得到當前線程
System.out.println("3" +Thread.currentThread().getName());
}
}
}); 
thread2.start();   //啟動線程 
}

注:執(zhí)行線程的時候可以使用start()方法或者是run()方法,雖然使用run方法會達到同樣的效果,但是run是在主線程中使用的,也就是使用了當前的方法內(nèi)線程,而不是另起一個線程,這樣就達不到異步的效果。所以務(wù)必使用start()。

  • 總結(jié):多線程并不一定會提高程序的運行效率。如:一個人同時在三張桌子做饅頭
    多線程下載:并不是自己電腦快了,而是搶到更多服務(wù)器資源。例:服務(wù)器為一個客戶分配一個20K的線程下載,你用多個線程,服務(wù)器以為是多個用戶就分配了多個20K的資源給你。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容