java 多线程同步_浅谈Java多线程(状态、同步等)
Java多線程是Java程序員必須掌握的基本的知識點,這塊知識點比較復雜,知識點也比較多,今天我們一一來聊下Java多線程,系統的整理下這部分內容。
一、Java中線程創建的三種方式:
1.通過繼承Thread類,重寫Thread的run()方法,將線程運行的邏輯放在其中
class MyThread extends Thread{
public void run(){
//do run something
}
}
public class ThreadDemo {
public static void main(String[] args) {
MyThread mt1= new MyThread();
MyThread mt2= new MyThread();
MyThread mt3= new MyThread();
mt1.start();
mt2.start();
mt3.start();
}
}
2.通過實現Runnable接口,實例化Thread類
//構造一個實現了Runnable接口的類
class MyThread1 implements Runnable{
public void run(){
//do run something
}
}
public class RunnableDemo {
public static void main(String[] args) {
//創建一個類對象
MyThread1 mt = new MyThread1();
//由Runnable創建Thread對象
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
//啟動線程
t1.start();
t2.start();
t3.start();
}
}
3.實現Callable接口,創建FutureTask包裝器,實例化Thread類
FutureTask實現接口類圖:
public interface Future {
//取消任務的運行
boolean cancel(boolean mayInterruptIfRunning);
//如果任務在完成前取消了返回true
boolean isCancelled();
//任務結束(正常結束、中途取消、發生異常),返回true
boolean isDone();
//返回最終計算完成的結果
V get() throws InterruptedException, ExecutionException;
//返回在指定時間內計算的結果,如果超過指定時間沒有結果則拋出TimeoutException異常
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
public interface Callable{
V call() throws Exception;
}
class MyThread2 implements Callable{
public Integer call(){
//do call something
}
}
public class CallableDemo{
public static void main(String[] args){
MyThread2 mt = new MyThread2();
FutureTask task = new FutureTask<>(mt);
Thread t = new Thread(task);
t.start();
Integer result = task.get();
}
}
注意:
1、不要直接調用Thread類或Runnable接口的run方法,直接調用run方法單純執行run方法體中的內容,而不會啟動新線程,應該調用Thread的start方法,這個方法將創建一個執行run方法的新線程。
2、盡量不要使用第一種方式來創建線程,因為有多個任務,這種方式需要為每個任務創建一個獨立的線程(new MyThread()),這個代價太大。
二、未捕獲異常處理器
我們知道在run方法中無法拋出任何不可查的異常,但一旦run方法中出現了這類異常則會直接導致線程終止,在這種情況下,線程就GG了。通過分析,我們知道在線程死亡之前,異常會被傳遞到一個用于未捕獲異常的處理器中,所以為了防止這種情況出現,我們可以為線程安裝一個未捕獲異常處理器。
未捕獲異常處理器必須實現Thread.UncaughtExceptionHanlder接口的類(這個接口類在Thread),這個類只有一個方法:
@FunctionalInterface
public interface UncaughtExceptionHandler {
void uncaughtException(Thread t, Throwable e);
}
通過Thread的靜態方法setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)為線程安裝一個默認的處理器。
當一個線程因為未捕獲異常而終止時,通過uncaughtException方法的System.err.print打印異常信息。
public void uncaughtException(Thread t, Throwable e) {
if (parent != null) {
parent.uncaughtException(t, e);
} else {
Thread.UncaughtExceptionHandler ueh =
Thread.getDefaultUncaughtExceptionHandler();
if (ueh != null) {
ueh.uncaughtException(t, e);
} else if (!(e instanceof ThreadDeath)) {
System.err.print("Exception in thread \""
+ t.getName() + "\" ");
e.printStackTrace(System.err);
}
}
}
三、線程狀態
線程的五個基本狀態:
新建(New)
可運行狀態(Runnable)
運行時狀態(Running)
阻塞狀態(Blocked)
死亡狀態(Dead)
線程調用start()方法開始后,就進入到可運行狀態,隨著CPU的資源調度在運行和可運行之間切換;遇到阻塞則進入阻塞狀態。這五種狀態的相互之間轉換圖如下圖所示:
線程被阻塞可能是由于下面五方面的原因:(《Thinking in Java》)
調用sleep(毫秒數),使線程進入睡眠狀態。在規定時間內,這個線程是不會運行的。
用suspend()暫停了線程的執行。除非收到resume()消息,否則不會返回“可運行”狀態。這兩個方法已經過時。
用wait()暫停了線程的執行。除非線程收到notify()或notifyAll()消息,否則不會變成“可運行”狀態。
線程正在等候一些IO操作完成。
線程試圖調用另一個對象的“同步”方法,但那個對象處于鎖定狀態,暫時無法使用。
如果線程中有同步方法,那么線程狀態圖如下圖所示:
當互斥資源被一個線程訪問時,互斥資源就上鎖了,這時候其他線程訪問該互斥資源就會進入了一個鎖池(Lock pool);當鎖被釋放,其他線程獲得了鎖,就變為可運行狀態。
如果線程調用了wait()等方法,那么線程狀態圖如下圖所示:
我們都知道線程調用了wait()(這個方法是Object的方法)方法之后,線程會釋放掉鎖,這個時候線程進入等待池(Wait pool);等線程收到通知之后等待獲取鎖,獲取鎖之后才可以運行。
四、線程同步
在Java中線程同步分五種方式:
synchronized
ReentrantLock與Condition
volatile
ThreadLocal
BlockingQueue
4.1 synchronized、wait與notify
當一個線程訪問用synchronized關鍵字修飾的代碼塊,如果這個代碼塊被該線程第一個訪問,則這個線程會獲取到該Java對象的內部鎖,其他線程訪問的時候則會因為獲取不到內部鎖而阻塞。synchronized可以修飾類方法(static修飾的方法)、實例方法和類(Object.class),但是不能修飾抽象類的抽象方法和接口中的接口方法。
線程在執行同步方法時是具有排它性的。當任意一個線程進入到一個對象的任意一個同步方法時,這個對象的所有同步方法都被鎖定了,在此期間,其他任何線程都不能訪問這個對象的任意一個同步方法,直到這個線程執行完它所調用的同步方法并從中退出,從而導致它釋放了該對象的同步鎖之后。在一個對象被某個線程鎖定之后,其他線程是可以訪問這個對象的所有非同步方法的。
我們知道wait()和notify()方法只能在加鎖的代碼塊中使用,因為調用wait()方法時會釋放所持有的對象的lock,同時進入等待狀態,notifyAll()方法會喚醒所有處入等待狀態的線程,注意并不是給所有喚醒線程一個對象的鎖,而是讓它們競爭。
4.2 ReentrantLock與Condition
ReentrantLock是可重入、互斥、實現了Lock接口的鎖,它與使用synchronized方法和快具有相同的基本行為和語義,并且擴展了其能力。其lock和unlock必須成對出現,否則可能會出現死鎖,通常在finally代碼釋放鎖。ReentrantLock()還有一個可以創建公平鎖的構造方法,但由于能大幅度降低程序運行效率,不推薦使用。
//fair為true時創建公平鎖
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
Condition代表條件對象,用于線程之間的通信,當一個線程A需要另一個線程B滿足一定條件才可以繼續操作時,A線程可以調用Condition的await()來阻塞當前線程,且放棄鎖,等到B線程執行了某些操作并滿足了一些條件后signalAll()喚醒阻塞的線程,當A線程重新強占了鎖資源后再變成可運行狀態。Condition條件對象對于一個對象來說可以有多個,但Object的wait()和notify()對于一個對象來說只有一個條件對象。
4.3 volatile
對于volatile修飾的變量,jvm虛擬機保證從主內存加載到線程工作內存的值是最新的。volatile可保證變量的可見性,但無法保證原子性。
4.4 ThreadLocal
多線程同步無非是讓原本多個線程對某個操作并行變成串行,我們必須小心地對共享資源進行同步,同步不僅會帶來一定的效能延遲,而且在處理同步的時候,又要注意對象的鎖定與釋放,稍有不慎就有可能產生死鎖。
既然這么麻煩,ThreadLocal不對共享資源加鎖,而是為每個線程創造一個資源的復本。將每一個線程存取數據的行為加以隔離,實現的方法就是給予每個線程一個特定空間來保管該線程所獨享的資源。
當使用ThreadLocal維護變量時,ThreadLocal為每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。從線程的角度看,目標變量就象是線程的本地變量,這也是類名中“Local”所要表達的意思。在 ThreadLocal 類中有一個 ThreadLocalMap ,用于存儲每一個線程的變量的副本。ThreadLocalMap是ThreadLocal類的一個靜態內部類,它實現了鍵值對的設置和獲取(對比Map對象來理解),每個線程中都有一個獨立的ThreadLocalMap副本,它所存儲的值,只能被當前線程讀取和修改。ThreadLocal類通過操作每一個線程特有的ThreadLocalMap副本,從而實現了變量訪問在不同線程中的隔離。
鎖是一種以時間換空間的機制,而ThreadLocal正好是以空間換時間的。
4.5 BlockingQueue
阻塞隊列在《淺談Java集合Collection》有提到過:多線程操作共同的隊列時不需要額外的同步,另外就是隊列會自動平衡負載,即那邊(生產與消費兩邊)處理快了就會被阻塞掉,從而減少兩邊的處理速度差距。
參考文獻:
《Java核心技術》
總結
以上是生活随笔為你收集整理的java 多线程同步_浅谈Java多线程(状态、同步等)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python如何创建模块教程_Pytho
- 下一篇: java 重复提交_java解决重复提交