Java】【并發編程】入門知識

      網友投稿 566 2022-05-30

      一、基本簡介

      什么是并發

      在Java中并發就是指多線程的進程環境,進程是系統進行資源分配和調度的獨立單位,每一個進程都有它的內存空間和系統資源,在同一個進程內執行的多個任務就可以看作是多個進程,線程存在于進程內,進程負責分配調度線程,線程負責執行程序,多個線程就執行多個程序。

      實際上,Java程序天生就是一個多線程程序,包含了:

      分發處理發送給JVM信號的線程

      調用對象的finalize清除方法的線程

      清除相互引用reference的線程

      main線程,也就是用戶程序的入口,main線程里面還可以擁有很多的子線程

      為什么需要多線程

      如果沒有多線程,若為了使程序并發執行,那么系統需要花費大量的時間在:創建進程-->撤銷進程-->進程上下文切換調度,在這一過程中,需要的空間開銷也非常大,執行效率也非常低(如下圖);若在一個進程中執行多個線程,則上面的空間開銷和時間花費將會大大較少,何樂而不為呢,多線程提高了系統的執行效率,充分利用多核CPU的計算能力,提高應用性能。

      二、并發編程帶來的問題

      頻繁的上下文切換問題

      正如上圖中的時間片,時間片使CPU分配給各個線程的時間,因為時間非常短,所以CPU需要不斷切換線程,讓我們覺得多個線程是同時執行的,時間片一般是十幾毫秒;每次切換都需要保存當前線程的狀態,以便進行恢復先前的狀態。這個切換是非常耗性能的,過于頻繁就無法發揮出多線程編程的優勢了。那么該怎么解決這頻繁的上下文切換的問題的,目前有大概幾種解決方法,后面會詳細討論:

      采用無鎖并發編程:JDK8以前的concurrentHashMap采用的鎖分段思想,不同線程處理不同段的數據,這樣在多線程環境下可以減少上下文的切換時間。

      采用CAS算法:JDK8以后的concurrentHashMap采用的是無鎖CAS算法;利用Atomic和樂觀鎖,可以減少一部分不必要的鎖競爭帶來的上下文切換。

      盡量減少線程的使用:避免創建不需要的線程,比如任務少,但是創建了很多的線程,這樣會造成大量的線程都處于等待狀態。

      采用協程:在單線程里實現多任務的調度,并在單線程里維持多個任務間的切換。

      因此,并發累加未必會比串行累加的速度快,這上下文切換的問題在實際中是需要解決的。

      線程安全問題(主要問題,也是我們程序開發關心的問題)

      對線程編程中最難控制的就是臨界區(共享內存的地方)的線程安全問題,稍微不注意就會出現死鎖的情況,一旦產生死鎖就會造成系統功能不可用。那么怎么解決這種問題呢,解決方法如下:

      避免一個線程同時獲取多個鎖

      避免一個線程在鎖內部占用多個資源,盡量保證一個鎖只占用一個資源

      嘗試使用定時鎖,如使用lock.tryLock(timeOut),當超時等待時當前線程也不會阻塞

      對于數據庫鎖,加鎖和解鎖必須在同一個數據庫連接里(同一個事務),否則會出現解鎖失敗的情況

      后面還有JMM內存模型在原子性、有序性和可見性帶來的問題,比如數據臟讀,內存泄漏等等問題,這是又該如何保證線程安全呢,這一方面是非常重要的,后面會詳細討論。

      三、并發編程的相關概念

      同步和異步

      同步和異步通常用來形容方法的一次調用。

      同步方法從被調用開始,調用者就必須等待被調用的方法結束后,調用者后面的代碼才能繼續執行。

      異步方法指的是,調用者不管被調用方法是否完成,都會繼續執行后面的代碼,當被調用的方法完成后會通知調用者。

      并發和并行

      并發是指多個任務線程交替進行。

      并行是指真正意義上的“同時進行”。

      實際上,如果系統只有一個CPU,而使用多線程時,那么真實環境下時不能并行執行的,只能通過切換時間片的方式交替進行,完成并發執行任務,真正的并行只能出現在擁有多個CPU系統中。

      阻塞和非阻塞

      阻塞和非阻塞通常用來形容多線程間的相互影響。

      阻塞是指如果一個線程占用了臨界區的資源,那么其他線程需要這個資源的話就必須等待資源的釋放,就會導致等待的線程掛起,這種情況就叫做阻塞。

      非阻塞剛好跟阻塞相反,它強調的是沒有一個線程可以阻塞其他線程,所有的線程都會嘗試的向前運行。

      臨界區

      臨界區用來表示一種公共資源會共享數據,可以被多個線程使用,出于線程安全問題,如果一個線程占用了臨界區的資源,那么其他線程就必須等待,知道臨界區的資源被釋放。

      守護線程

      守護線程是一種特殊的線程,是系統的服務線程,是專門為其他線程服務的,像垃圾回收線程就是守護線程,與之對應的是用戶線程,用戶線程作為系統的工作線程,守護線程的服務對象就是用戶線程,當全部的用戶線程執行任務完成之后,這個系統就沒有什么需要服務的了,那么守護線程就沒有對象需要守護了,那么守護線程就會結束,也就是說當一個java程序只有守護線程的時候,虛擬機就會退出了。

      四、Java中的線程Thread類

      參考看一下Thread類的源碼注釋,了解Java中的線程:

      /**

      1.一個Thread類對象代表程序中的一個線程,jvm是允許多線程的

      * A thread is a thread of execution in a program. The Java

      * Virtual Machine allows an application to have multiple threads of

      * execution running concurrently.

      *

      2.每一個線程都有優先級,具有高優先級的線程優先于底優先級的線程執行,每一個線程都可以設置成一個守護線程,創建線程的時候,通過線程設置setDaemon(true)就可以設置該線程為守護線程,設置守護線程需要先于start()方法

      * Every thread has a priority. Threads with higher priority are

      * executed in preference to threads with lower priority. Each thread

      * may or may not also be marked as a daemon. When code running in

      * some thread creates a new Thread object, the new

      * thread has its priority initially set equal to the priority of the

      * creating thread, and is a daemon thread if and only if the

      * creating thread is a daemon.

      *

      2.只有當一個Java程序只存在守護線程的時候,虛擬機就會退出,讓虛擬機不繼續執行線程的方法有:

      2.1調用system.exit方法.

      2.2所有非守護線程都處于死亡狀態(只有守護線程)或線程運行拋出了異常

      注意:在線程啟動前可以將該線程設置為守護線程,方法是setDaemon(boolean on)

      使用守護線程最好不要在方法中使用共享資源,因為守護線程隨時都可能掛掉

      在守護線程中產生的線程也是守護線程

      * When a Java Virtual Machine starts up, there is usually a single

      * non-daemon thread (which typically calls the method named

      * main of some designated class). The Java Virtual

      * Machine continues to execute threads until either of the following

      * occurs:

      *

        *

      • The exit method of class Runtime has been

        *???? called and the security manager has permitted the exit operation

        *???? to take place.

        *

      • All threads that are not daemon threads have died, either by

        *???? returning from the call to the run method or by

        *???? throwing an exception that propagates beyond the run

        *???? method.

        *

      *

      3.創建線程的方式有兩種(重寫Runnable接口的run()方法):

      3.1創建子類并繼承Thread 類,同時重寫run()方法(因為Thread類實現了Runnable接口)

      3.2創建子類并實現Runnable接口,同時重寫run()方法

      下面有例子:

      * There are two ways to create a new thread of execution. One is to

      * declare a class to be a subclass of Thread. This

      * subclass should override the run method of class

      * Thread. An instance of the subclass can then be

      * allocated and started. For example, a thread that computes primes

      * larger than a stated value could be written as follows:

      *


      *???? class PrimeThread extends Thread {

      *???????? long minPrime;

      *???????? PrimeThread(long minPrime) {

      *???????????? this.minPrime = minPrime;

      *???????? }

      *

      *???????? public void run() {

      *???????????? // compute primes larger than minPrime

      *????????????  . . .

      *???????? }

      *???? }

      *


      *

      * The following code would then create a thread and start it running:

      *

      *???? PrimeThread p = new PrimeThread(143);

      *???? p.start();

      *

      *

      * The other way to create a thread is to declare a class that

      * implements the Runnable interface. That class then

      * implements the run method. An instance of the class can

      * then be allocated, passed as an argument when creating

      * Thread, and started. The same example in this other

      * style looks like the following:

      *


      *???? class PrimeRun implements Runnable {

      *???????? long minPrime;

      *???????? PrimeRun(long minPrime) {

      *???????????? this.minPrime = minPrime;

      *???????? }

      *

      *???????? public void run() {

      *???????????? // compute primes larger than minPrime

      *????????????  . . .

      *???????? }

      *???? }

      *


      *

      * The following code would then create a thread and start it running:

      *

      *???? PrimeRun p = new PrimeRun(143);

      *???? new Thread(p).start();

      *

      【Java】【并發編程】入門知識

      *

      4.每個線程都有一個名稱,如果沒有會在創建的時候自動生成一個,除非指定為null。

      * Every thread has a name for identification purposes. More than

      * one thread may have the same name. If a name is not specified when

      * a thread is created, a new name is generated for it.

      *

      * Unless otherwise noted, passing a {@code null} argument to a constructor

      * or method in this class will cause a {@link NullPointerException} to be

      * thrown.

      */

      五、總結

      我們需要了解并發,為什么需要并發,還必須知道并發的優缺點,同時清楚使用并發編程之后所帶來的問題:頻繁上下文切換問題和線程安全問題等等,后面在并發編程的時候就朝著這些問題去編程,嘗試解決這些問題,讓并發編程發揮出真正的作用。

      理解Java并發的關鍵點在于理解它的兩大核心(JMM內存模型【工作內存和主內存】和happes-before規則【八大規則】)以及三大特性:原子性、可見性、有序性

      Java 任務調度

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:Python 中級知識之裝飾器,滾雪球學 Python
      下一篇:人從眾,越來越多的企業基于華為云走向全局創新
      相關文章
      国产亚洲精品VA片在线播放| 亚洲xxxx视频| 亚洲国产美女在线观看| 亚洲色偷偷综合亚洲AVYP| 亚洲精品尤物yw在线影院| yy6080久久亚洲精品| 亚洲精品人成网线在线播放va| 亚洲一级毛片在线播放| 亚洲天堂福利视频| 亚洲人成网网址在线看| 亚洲三级在线免费观看| 亚洲国产美女精品久久| 亚洲av无码片区一区二区三区| 亚洲人成免费网站| 亚洲av乱码一区二区三区香蕉| 亚洲伊人久久大香线蕉啊| 亚洲av永久综合在线观看尤物| 亚洲av一本岛在线播放| 中文字幕亚洲综合久久综合| 亚洲精品456人成在线| 国产午夜亚洲精品| 亚洲成在人线aⅴ免费毛片| 亚洲经典千人经典日产| 成人亚洲国产精品久久| 亚洲精品成人a在线观看| 精品国产人成亚洲区| 亚洲精品少妇30p| 久久精品亚洲综合| 亚洲精品自拍视频| 亚洲伊人久久大香线蕉结合| 亚洲色无码国产精品网站可下载| 亚洲国产美女精品久久久| 精品韩国亚洲av无码不卡区| 亚洲黄黄黄网站在线观看| 国产亚洲精品线观看动态图| 亚洲成AV人片在线观看无| 亚洲精彩视频在线观看| 2020久久精品亚洲热综合一本 | 亚洲人成网站在线在线观看| 亚洲Av永久无码精品黑人| 亚洲福利在线播放|