【Java】【并發編程】入門知識
一、基本簡介
什么是并發
在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 classRuntime
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();
*
*
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小時內刪除侵權內容。