Java併發基礎總結

  併發是一種能並行運行多個程序或並行運行一個程序中多個部分的能力。若是程序中一個耗時的任務能以異步或並行的方式運行,那麼整個程序的吞吐量和可 交互性將大大改善。現代的PC都有多個CPU或一個CPU中有多個核,是否能合理運用多核的能力將成爲一個大規模應用程序的關鍵。html

線程基本使用

  編寫線程運行時執行的代碼有兩種方式:一種是建立Thread子類的一個實例並重寫run方法,第二種是建立類的時候實現Runnable接口。固然,實現 Callable也算是一種方式,Callable和Future結合實現能夠實如今執行完任務後獲取返回值,而Runnable和Thread方式是沒法獲取任務執行後的結果的。java

public class ThreadMain {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        new Thread(myThread).start();

        new MyThreas2().start();
    }
}

// 第一種方式,實現Runable接口
class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println("MyThread run...");
    }
}

// 第二種方式,繼承Thread類,重寫run()方法
class MyThreas2 extends Thread {
    @Override
    public void run() {
        System.out.println("MyThread2 run...");
    }
}

  一旦線程啓動後start()方法會當即返回,而不會等待run()方法執行完畢後返回,就好像run方法是在另一個cpu上執行同樣。編程

注意:建立並運行一個線程所犯的常見錯誤是調用線程的run()方法而非start()方法,以下所示:數組

Thread newThread = new Thread(MyRunnable());
newThread.run();  //should be start();

  起初你並不會感受到有什麼不妥,由於run()方法的確如你所願的被調用了。可是,事實上,run()方法並不是是由剛建立的新線程所執行的,而是當前線程所執行了。也就是被執行上面兩行代碼的線程所執行的。想要讓建立的新線程執行run()方法,必須調用新線程的start方法。緩存

Callable和Future結合實現實如今執行完任務後獲取返回值多線程

public static void main(String[] args) {
    ExecutorService exec = Executors.newSingleThreadExecutor();
    Future<String> future = exec.submit(new CallTask());

    System.out.println(future.get());
}
class CallTask implements Callable {
    public String call() {
        return "hello";
    }
}

給線程設置線程名:併發

MyTask myTask = new MyTask();
Thread thread = new Thread(myTask, "myTask thread");

thread.start();
System.out.println(thread.getName());

  當建立一個線程的時候,能夠給線程起一個名字。它有助於咱們區分不一樣的線程。異步

 

volatile

  在多線程併發編程中synchronized和Volatile都扮演着重要的角色,Volatile是輕量級的synchronized,它在多處理器開發中保證了共享變量的「可見性」。可見性的意思是當一個線程修改一個共享變量時,另一個線程能讀到這個修改的值。它在某些狀況下比synchronized的開銷更小,可是volatile不能保證變量的原子性。ide

  volatile變量進行寫操做時(彙編下有lock指令),該lock指令在多核系統下有2個做用:優化

  • 將當前CPU緩存行寫回系統內存。
  • 這個寫回操做會引發其餘CPU緩存了改地址的數據失效。

  多CPU下遵循緩存一致性原則,每一個CPU經過嗅探在總線上傳播的數據來檢查本身的緩存值是否過時了,當發現緩存對應的內存地址被修改,將對應緩存行設置爲無效狀態,下次對數據操做會從系統內存從新讀取。更多volatile知識請點擊深刻分析Volatile的實現原理

 

synchronized

  在多線程併發編程中Synchronized一直是元老級角色,不少人都會稱呼它爲重量級鎖,可是隨着Java SE1.6對Synchronized進行了各類優化以後,有些狀況下它並不那麼重了。

  Java中每個對象均可以做爲鎖,當一個線程試圖訪問同步代碼塊時,它首先必須獲得鎖,退出或拋出異常時必須釋放鎖。

  • 對於同步方法,鎖是當前實例對象。
  • 對於靜態同步方法,鎖是當前對象的Class對象。
  • 對於同步方法塊,鎖是Synchonized括號裏配置的對象。

  synchronized關鍵字是不能繼承的,也就是說基類中的synchronized方法在子類中默認並非synchronized的。當線程試圖訪問同步代碼塊時,必須先得到鎖,退出或拋出異常時釋放鎖。Java中每一個對象均可以做爲鎖,那麼鎖存在哪裏呢?鎖存在Java對象頭中,若是對象是數組類型,則虛擬機用3個word(字寬) 存儲對象頭,若是對象是非數組類型,則用2字寬存儲對象頭。更多synchronized知識請點擊Java SE1.6中的Synchronized

 

線程池

  線程池負責管理工做線程,包含一個等待執行的任務隊列。線程池的任務隊列是一個Runnable集合,工做線程負責從任務隊列中取出並執行Runnable對象。

ExecutorService executor  = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
    executor.execute(new MyThread2());
}
executor.shutdown();

Java經過Executors提供了4種線程池:

  • newCachedThreadPool:建立一個可緩存線程池,對於新任務若是沒有空閒線程就新建立一個線程,若是空閒線程超過必定時間就會回收。
  • newFixedThreadPool:建立一個固定數量線程的線程池。
  • newSingleThreadExecutor:建立一個單線程的線程池,該線程池只用一個線程來執行任務,保證全部任務都按照FIFO順序執行。
  • newScheduledThreadPool:建立一個定長線程池,支持定時及週期性任務執行。

  以上幾種線程池底層都是調用ThreadPoolExecutor來建立線程池的。

ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue)
  • corePoolSize(線程池的基本大小):當提交一個任務到線程池時,線程池會建立一個線程來執行任務,即便其餘空閒的基本線程可以執行新任務也會建立線程,等到須要執行的任務數大於線程池基本大小時就再也不建立。若是調用了線程池的prestartAllCoreThreads方法,線程池會提早建立並啓動全部基本線程。
  • maximumPoolSize(線程池最大大小):線程池容許建立的最大線程數。若是隊列滿了,而且已建立的線程數小於最大線程數,則線程池會再建立新的線程執行任務。值得注意的是若是使用了無界的任務隊列這個參數就沒什麼效果。
  • keepAliveTime(線程活動保持時間):線程池的工做線程空閒後,保持存活的時間。因此若是任務不少,而且每一個任務執行的時間比較短,能夠調大這個時間,提升線程的利用率。
  • TimeUnit(線程活動保持時間的單位):可選的單位有天(DAYS),小時(HOURS),分鐘(MINUTES),毫秒(MILLISECONDS),微秒(MICROSECONDS, 千分之一毫秒)和毫微秒(NANOSECONDS, 千分之一微秒)。,能夠選擇的阻塞隊列有如下幾種:
  • workQueue(任務隊列):用於保存等待執行的任務的阻塞隊列。

 

    • ArrayBlockingQueue:是一個基於數組結構的有界阻塞隊列,此隊列按 FIFO(先進先出)原則對元素進行排序。
    • LinkedBlockingQueue:一個基於鏈表結構的阻塞隊列,此隊列按FIFO (先進先出) 排序元素,吞吐量一般要高於ArrayBlockingQueue。靜態工廠方法Executors.newFixedThreadPool()使用了這個隊列。
    • SynchronousQueue:一個不存儲元素的阻塞隊列。每一個插入操做必須等到另外一個線程調用移除操做,不然插入操做一直處於阻塞狀態,吞吐量一般要高於LinkedBlockingQueue,靜態工廠方法Executors.newCachedThreadPool使用了這個隊列。
    • PriorityBlockingQueue:一個具備優先級得無限阻塞隊列。

當提交新任務到線程池時,其處理流程以下:

  1. 先判斷基本線程池是否已滿?沒滿則建立一個工做線程來執行任務,滿了則進入下個流程。
  2. 其次判斷工做隊列是否已滿?沒滿則提交新任務到工做隊列中,滿了則進入下個流程。
  3. 最後判斷整個線程池是否已滿?沒滿則建立一個新的工做線程來執行任務,滿了則交給飽和策略來處理這個任務。

 

參考:

  一、深刻分析Volatile的實現原理

  二、Java SE1.6中的Synchronized

相關文章
相關標籤/搜索