天天看點

探讨一下!Java并發程式設計基礎篇一

探讨一下!Java并發程式設計基礎篇一

Java并發程式設計想必大家都不陌生,它是實作高并發/高流量的基礎,今天我們就來一起學習這方面的内容。

什麼是線程?什麼是程序?他們之間有什麼聯系?

簡單來說,程序就是程式的一次執行過程,它是系統進行資源配置設定和排程的基本機關。線程與程序類似,但是線程是更小的執行機關。一個程序在執行過程中可産生多個線程,正因為如此,線程也被稱為輕量級的程序 。線程和程序在于程序大多是獨立工作的,而各線程則不一定,同一程序下的線程極有可能互相幹擾。學習了以上内容,回答第一個問題想必就不在話下了。

Talk is cheap,shou me the code.接下來就讓我們通過建立線程,來開啟我們的并發之旅吧。

線程的建立

建立線程通常有三種方式,分别為繼承 Thread 類(重寫run方法),實作Runable接口以及使用FutureTask方式。

繼承Thread類

public class MyThread extends Thread {
 @Override
 public void run() {
 System.out.println(Thread.currentThread()+"I am a new thread ");
 }
 
 public static void main(String[] args) {
 MyThread thread1=new MyThread();
 MyThread thread2=new MyThread();
 thread1.start();
 thread2.start();
 }
}
           

如上述的代碼所示,我們繼承Thread并且重寫了run方法,并開啟了兩個線程。

該代碼執行結果如下:

Thread[Thread-0,5,main]I am a new thread Thread[Thread-1,5,main]I am a new thread
           

值得一提的是,建立完Thread對象後線程并沒有啟動,直到調用了start方法才真正啟動。

那麼問題就來了, 我們調用 start() 方法時會執行 run() 方法,為什麼我們不能直接調用 run() 方法?

我們new一個Thread,線程就進入了建立狀态,而調用了start方法之後它才會進入就緒狀态,等待CPU時間片的配置設定。就緒狀态+時間片=線程真正運作,它會自動執行run方法。如果直接調用run方法,main線程會把它當做一個普通的方法去執行,這不是多線程工作(仍然在主線程執行)。

實作Runnable接口

public class MyThread implements Runnable {
     @Override
     public void run() {
     System.out.println(Thread.currentThread() + "I am a new thread ");
     }
    }
     
     public static void main(String[] args) {
     MyThread thread1 = new MyThread();
     MyThread thread2 = new MyThread();
     new Thread(thread1).start();
     new Thread(thread2).start();
     }
           

此代碼執行結果和代碼一相似,由于Java隻支援單繼承,如果使用繼承Thread的方式就不能再繼承其他的類,而Runnable沒有這個限制,但是他們有一個共同的缺點,任務沒有傳回值,我們接着來看最後一種方式。

使用FutureTask

public class CallerTask implements Callable<String> {
 @Override
 public String call() throws Exception {
 return Thread.currentThread() + " hello world";
 }
}
 public static void main(String[] args) {
 FutureTask<String> futureTask1 = new FutureTask<>(new CallerTask());
 FutureTask<String> futureTask2 = new FutureTask<>(new CallerTask());
 new Thread(futureTask1).start();
 new Thread(futureTask2).start();
 try {
 String result1 = futureTask1.get();
 String result2 = futureTask2.get();
 System.out.println(result1);
 System.out.println(result2);
 } catch (InterruptedException e) {
 e.printStackTrace();
 } catch (ExecutionException e) {
 e.printStackTrace();
 }
 }
           

該代碼執行結果如下:

Thread[Thread-0,5,main] hello world Thread[Thread-1,5,main] hello world
           

如上述代碼所示,我們實作了Callable接口的call()方法,在main函數中建立了兩個FurtureTask對象,并且将其啟動。通過futureTask.get()方法拿到了我們的傳回值。

并發程式的常用方法

wait() 方法

當一個線程調用一個共享變量的wait方法的時候,該線程會被阻塞挂起。如果wait() 方法事先沒有擷取鎖,那麼它就會抛出異常。 如果發生了以下幾種情況,wait狀态将會被打破。

其他線程調用了該共享對象的notify() 或notifyAll()方法

其他線程調用了該線程的interrupt() 方法

目前線程調用共享對象的wait() 方法的時候,目前線程隻會釋放目前共享對象的鎖,目前線程持有的其他共享對象的鎖不會被釋放。

notify() 方法

一個線程調用共享對象的notify() 方法之後,會喚醒一個處于等待狀态的線程,它并不會确定的喚醒某個線程,而是由JVM決定。被喚醒的線程并不能繼續執行,因為它的鎖狀态已經發生了改變,必須去競争擷取鎖,才能進入就緒狀态。

notifyAll() 方法

notifyAll()方法會喚醒該共享變量上所有處于等待狀态的線程,但是值得注意的是,它隻會喚醒調用這個方法之前被阻塞的線程。之後阻塞的它就無能為力了。

sleep方法

調用該方法會暫時讓出指定時間的執行權,也就是在此期間不參與CPU的排程,但是該線程所擁有的螢幕資源,畢如鎖還是持有不讓出的。到了指定時間之後,他就會處于就緒狀态,等待擷取CPU,重新進入運作狀态。

接下類我們談談sleep()和wait()的聯系和差別

最重要的一點就是sleep沒有釋放鎖,而wait釋放了鎖

兩者都可以暫停線程的執行

wait()常常用于線程之間的互動/通信

wait() 方法被調用後,線程不會自動蘇醒,需要别的線程調用同一個對象上的 notify() 或者 notifyAll() 方法。sleep() 方法執行完成後,線程會自動蘇醒。

本篇文章主要介紹了線程的建立以及并發過程的常用方法,希望認真讀完的你有所收獲。

第一:看完點贊,感謝您的認可;

第二:随手轉發,分享知識,讓更多人學習到;

第三:記得點關注,每天更新的!!!