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方法,並開啓了兩個線程。ide

該代碼執行結果以下:函數

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() 方法?spa

咱們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() 方法執行完成後,線程會自動甦醒。

本篇文章主要介紹了線程的建立以及併發過程的經常使用方法,但願認真讀完的你有所收穫。

相關文章
相關標籤/搜索