java併發終極總結

目錄

 

Java併發介紹

1.併發編程三要素

2. 線程的五大狀態

3.悲觀鎖與樂觀鎖

4.線程之間的協作

valitate 關鍵字

5.1 定義

5.2 原理

5.3 作用

Future

cancel方法

isCancelled

isDone

get()

get(long timeout, TimeUnit unit)

FutureTask

FutureTask與Future使用案例

Executor框架

ExecutorService

Executor 和 ExecutorService 和 Executors的區別

ThreadFactory接口

ThreadLocal

Semaphore

簡介:

Interrupt與stop

理解

正確停止線程

線程池

線程池的好處

線程池的功能

常用線程池

ExecutorService介紹

ExecutorService的創建

ExecutorService的使用

ExecutorService的執行

ExecutorService的關閉

Java線程池中submit() 和 execute()方法有什麼區別?

線程池的種類,區別和使用場景

CountDownLatch

CountDownLatch是什麼

CountDownLatch如何工作

在實時系統中的使用場景

 CyclicBarrier

CyclicBarrier是什麼

CyclicBarrier如何使用

CountDownLatch與CyclicBarrier區別

如何保證線程順序執行

方法一:通過共享對象鎖加上可見變量來實現。

方法二:通過主線程Join()

方法三:通過線程執行時Join()

Lock 與Synchronized的區別

Delay Quene

簡介

Delayed

延時隊列的實現

1.消息體

2.消費者

3.延時隊列

 


Java併發介紹

1.併發編程三要素

原子性原子,即一個不可再被分割的顆粒。在Java中原子性指的是一個或多個操作要麼全部執行成功要麼全部執行失敗。

有序性程序執行的順序按照代碼的先後順序執行。(處理器可能會對指令進行重排序)

可見性當多個線程訪問同一個變量時,如果其中一個線程對其作了修改,其他線程能立即獲取到最新的值。

2. 線程的五大狀態

創建狀態當用 new 操作符創建一個線程的時候

就緒狀態調用 start 方法,處於就緒狀態的線程並不一定馬上就會執行 run 方法,還需要等待CPU的調度

運行狀態CPU 開始調度線程,並開始執行 run 方法

阻塞狀態線程的執行過程中由於一些原因進入阻塞狀態比如:調用 sleep 方法、嘗試去得到一個鎖等等​​

死亡狀態run 方法執行完 或者 執行過程中遇到了一個異常

3.悲觀鎖與樂觀鎖

悲觀鎖:每次操作都會加鎖,會造成線程阻塞。

樂觀鎖:每次操作不加鎖而是假設沒有衝突而去完成某項操作,如果因爲衝突失敗就重試,直到成功爲止,不會造成線程阻塞。​

4.線程之間的協作

4.1 wait/notify/notifyAll

均是Object 類的方法需要注意的是:這三個方法都必須在同步的範圍內調用​

wait阻塞當前線程,直到 notify 或者 notifyAll 來喚醒​

valitate 關鍵字

5.1 定義

java編程語言允許線程訪問共享變量,爲了確保共享變量能被準確和一致的更新,線程應該確保通過排他鎖單獨獲得這個變量。Java語言提供了volatile,在某些情況下比鎖更加方便。如果一個字段被聲明成volatile,java線程內存模型確保所有線程看到這個變量的值是一致的。

valitate是輕量級的synchronized,不會引起線程上下文的切換和調度,執行開銷更小。

5.2 原理

1. 使用volitate修飾的變量在彙編階段,會多出一條lock前綴指令2. 它確保指令重排序時不會把其後面的指令排到內存屏障之前的位置,也不會把前面的指令排到內存屏障的後面;即在執行到內存屏障這句指令時,在它前面的操作已經全部完成3. 它會強制將對緩存的修改操作立即寫入主存4. 如果是寫操作,它會導致其他CPU裏緩存了該內存地址的數據無效

5.3 作用

內存可見性多線程操作的時候,一個線程修改了一個變量的值 ,其他線程能立即看到修改後的值防止重排序即程序的執行順序按照代碼的順序執行(處理器爲了提高代碼的執行效率可能會對代碼進行重排序)

Future

Future就是對於具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。必要時可以通過get方法獲取執行結果,該方法會阻塞直到任務返回結果

Future類位於java.util.concurrent包下,它是一個接口


public interface Future<V> {
   boolean cancel(boolean mayInterruptIfRuning);
    boolean isCancelled();
    boolean isDone();
    V get() throws InterruptedException, ExecutionException;
       V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

在Future接口中聲明瞭5個方法,下面依次解釋每個方法的作用:

cancel方法

用來取消任務,如果取消任務成功則返回true,如果取消任務失敗則返回false。參數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設置true,則表示可以取消正在執行過程中的任務。如果任務已經完成,則無論mayInterruptIfRunning爲true還是false,此方法肯定返回false,即如果取消已經完成的任務會返回false;如果任務正在執行,若mayInterruptIfRunning設置爲true,則返回true,若mayInterruptIfRunning設置爲false,則返回false;如果任務還沒有執行,則無論mayInterruptIfRunning爲true還是false,肯定返回true。

isCancelled

方法表示任務是否被取消成功,如果在任務正常完成前被取消成功,則返回 true。

isDone

方法表示任務是否已經完成,若任務完成,則返回true;

get()

方法用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回;

get(long timeout, TimeUnit unit)

用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null。

也就是說Future提供了三種功能:

1)判斷任務是否完成;

2)能夠中斷任務;

3)能夠獲取任務執行結果。

因爲Future只是一個接口,所以是無法直接用來創建對象使用的,因此就有了下面的FutureTask。

FutureTask

我們先來看一下FutureTask的實現:

pu

blic class FutureTask<V> implements RunnableFuture<V> {

FutureTask類實現了RunnableFuture接口,我們看一下RunnableFuture接口的實現:

public interface RunnableFuture<V> extends Runnable, Future<V> {
       void run();
}

可以看出RunnableFuture繼承了Runnable接口和Future接口,而FutureTask實現了RunnableFuture接口。所以它既可以作爲Runnable被線程執行,又可以作爲Future得到Callable的返回值。


FutureTask提供了2個構造器:

public FutureTask(Callable<V> callable) {
public FutureTask(Runnable runnable, V result) {

事實上,FutureTask是Future接口的一個唯一實現類。

FutureTaskFuture使用案例

public class Test {
  public static void main(String[] args) {
    ExecutorService executor = Executors.newCachedThreadPool();
    Task task = new Task();
    Future<Integer> result = executor.submit(task);
    executor.shutdown();

    try {
      Thread.sleep(1000);
    } catch (InterruptedException e1) {
      e1.printStackTrace();
    }

    System.out.println("主線程在執行任務");

    try {
      System.out.println("task運行結果"+result.get());
    } catch (InterruptedException e) {
      e.printStackTrace();
    } catch (ExecutionException e) {
      e.printStackTrace();
    }
    System.out.println("所有任務執行完畢");
  }
}
class Task implements Callable<Integer>{
  @Override
  public Integer call() throws Exception {
    System.out.println("子線程在進行計算");
    Thread.sleep(3000);
    int sum = 0;
    for(int i=0;i<100;i++)
      sum += i;
    return sum;
  }
}

FutureTask使用案例

public class Test {
  public static void main(String[] args) {
    //第一種方式
    ExecutorService executor = Executors.newCachedThreadPool();
    Task task = new Task();
    FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
    executor.submit(futureTask);
    executor.shutdown();

    //第二種方式,注意這種方式和第一種方式效果是類似的,只不過一個使用的是ExecutorService,一個使用的是Thread
    /*Task task = new Task();
    FutureTask<Integer> futureTask = new FutureTask<Integer>(task);
    Thread thread = new Thread(futureTask);
    thread.start();*/

    try {
      Thread.sleep(1000);
    } catch (InterruptedException e1) {
      e1.printStackTrace();
    }

    System.out.println("主線程在執行任務");

    try {
      System.out.println("task運行結果"+futureTask.get());
    } catch (InterruptedException e) {
      e.printStackTrace();
    } catch (ExecutionException e) {
      e.printStackTrace();
    }

    System.out.println("所有任務執行完畢");
  }
}
class Task implements Callable<Integer>{
  @Override
  public Integer call() throws Exception {
    System.out.println("子線程在進行計算");
    Thread.sleep(3000);
    int sum = 0;
    for(int i=0;i<100;i++)
      sum += i;
    return sum;
  }
}

Executor框架

叫執行器,用於管理線程,是線程池的頂級接口

ExecutorService

繼承自Executor接口

主要方法

void execute(Runnable runnable)執行一個不需要返回值得線程。

Future<T> submit(Callable callable) 執行有返回值的線程。返回Future對象.

Future<T> submit(Runnable runnable) 執行沒有返回值的線程並返回Future對象

Future<T> submit(Runnable runnable,T result)執行沒有返回值的線程。如果線程執行成功則返回預設的result.

Set<Future<T>> invokeAll(Set<Callable> set);執行一個集合的有返回值的線程。

Set<Future<T>> invokeAll(Set<Callable> set,Long time,TimeUnit t);在指定的時間內執行集合的方法,如果指定時間內還沒有獲取結果,那麼終止該線程執行。返回的Future對象可通過isDone()方法和isCancel()來判斷是執行成功還是被終止了

Executor 和 ExecutorService  Executors的區別

Executors 類提供工廠方法用來創建不同類型的線程池。比如: newSingleThreadExecutor() 創建一個只有一個線程的線程池,newFixedThreadPool(int numOfThreads)來創建固定線程數的線程池,newCachedThreadPool()可以根據需要創建新的線程,但如果已有線程是空閒的會重用已有線程。

ThreadFactory接口

ThreadFactory翻譯過來是線程工廠,顧名思義,就是用來創建線程的,它用到了工廠模式的思想。它通常和線程池一起使用,主要用來控制創建新線程時的一些行爲,比如設置線程的優先級,名字等等。它是一個接口,接口中只有一個方法:

Thread newThread(Runnable r);

一般和ExecutorService一起使用

final ThreadFactory threadFactory = new ThreadFactoryBuilder()
      .setNameFormat("Orders-%d")
      .setDaemon(true)
      .build();
final ExecutorService executorService = Executors.newFixedThreadPool(10, threadFactory);

ThreadLocal

核心點:就是一個變量 通過ThreadLocal.get()去在不同的線程中使用,且互不影響。

在高併發場景,如果只考慮線程安全而不考慮延遲性、數據共享的話,那麼使用ThreadLocal會是一個非常不錯的選擇。當使用ThreadLocal維護變量時,ThreadLocal爲每個使用該變量的線程提供獨立的變量副本,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應的副本。

理解:

對於多線程資源共享的問題,同步機制採用了“以時間換空間”的方式,比如定義一個static變量,同步訪問,而ThreadLocal採用了“以空間換時間”的方式。前者僅提供一份變量,讓不同的線程排隊訪問,而後者爲每一個線程都提供了一份變量,因此可以同時訪問而互不影響。

注意點:

也就是說這個類給線程提供了一個本地變量,這個變量是該線程自己擁有的。在該線程存活和ThreadLocal實例能訪問的時候,保存了對這個變量副本的引用.當線程消失的時候,所有的本地實例都會被GC。並且建議我們ThreadLocal最好是 private static 修飾的成員

ThreadLocal和Synchonized區別:

都用於解決多線程併發訪問。

Synchronized用於線程間的數據共享(使變量或代碼塊在某一時該只能被一個線程訪問),是一種以延長訪問時間來換取線程安全性的策略;

而ThreadLocal則用於線程間的數據隔離(爲每一個線程都提供了變量的副本),是一種以空間來換取線程安全性的策略

ThreadLocal是如何做到爲每一個線程維護變量的副本的呢?

其實實現的思路很簡單:在ThreadLocal類中有一個Map,用於存儲每一個線程的變量副本,Map中元素的鍵爲線程對象,而值對應線程的變量副本

當然ThreadLocal並不能替代同步機制,兩者面向的問題領域不同。同步機制是爲了同步多個線程對相同資源的併發訪問,是爲了多個線程之間進行通信的有效方式;而ThreadLocal是隔離多個線程的數據共享,從根本上就不在多個線程之間共享資源(變量),這樣當然不需要對多個線程進行同步了。所以,如果你需要進行多個線程之間進行通信,則使用同步機制;如果需要隔離多個線程之間的共享衝突,可以使用ThreadLocal,這將極大地簡化你的程序,使程序更加易讀、簡潔。

常用方法:

void set(Object value)

設置當前線程的線程局部變量的值。

public Object get()

該方法返回當前線程所對應的線程局部變量。

public void remove()

將當前線程局部變量的值刪除,目的是爲了減少內存的佔用,該方法是JDK 5.0新增的方法。需要指出的是,當線程結束後,對應該線程的局部變量將自動被垃圾回收,所以顯式調用該方法清除線程的局部變量並不是必須的操作,但它可以加快內存回收的速度。

protected Object initialValue()

返回該線程局部變量的初始值,該方法是一個protected的方法,顯然是爲了讓子類覆蓋而設計的。這個方法是一個延遲調用方法,在線程第1次調用get()或set(Object)時才執行,並且僅執行1次。ThreadLocal中的缺省實現直接返回一個null。

用法:舉例

 public class SequenceNumber {
①通過匿名內部類覆蓋ThreadLocal的initialValue()方法,指定初始值
    private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){
      public Integer initialValue(){
        return 0;
      }
    };
②獲取下一個序列值
    public int getNextNum(){
      seqNum.set(seqNum.get()+1);
      return seqNum.get();
    }
    public static void main(String[] args)
    {
      SequenceNumber sn = new SequenceNumber();
③ 3個線程共享sn,各自產生序列號
      TestClient t1 = new TestClient(sn)
      TestClient t2 = new TestClient(sn);
      TestClient t3 = new TestClient(sn);
      t1.start();
      t2.start();
      t3.start();
    }
    private static class TestClient extends Thread
    {
      private SequenceNumber sn;
      public TestClient(SequenceNumber sn) {
        this.sn = sn
      }
      public void run()
      {
        for (int i = 0; i < 3; i++) {④每個線程打出3個序列值
          System.out.println("thread["+Thread.currentThread().getName()+
         "] sn["+sn.getNextNum()+"]");

        }
}}
}
  

通常我們通過匿名內部類的方式定義ThreadLocal的子類,提供初始的變量值,如例子中①處所示。TestClient線程產生一組序列號,在③處,我們生成3TestClient,它們共享同一個SequenceNumber實例。運行以上代碼,在控制檯上輸出以下的結果:

thread[Thread-2] sn[1]
thread[Thread-0] sn[1]
thread[Thread-1] sn[1]
thread[Thread-2] sn[2]
thread[Thread-0] sn[2]
thread[Thread-1] sn[2]
thread[Thread-2] sn[3]
thread[Thread-0] sn[3]
thread[Thread-1] sn[3]

考察輸出的結果信息,我們發現每個線程所產生的序號雖然都共享同一個SequenceNumber實例,但它們並沒有發生相互干擾的情況,而是各自產生獨立的序列號,這是因爲我們通過ThreadLocal爲每一個線程提供了單獨的副本。

Semaphore

簡介:

Semaphore又稱信號量,是操作系統中的一個概念,在Java併發編程中,信號量控制的是線程併發的數量。從概念上講,信號量維護了一個許可集合,如有必要,在許可可用前會阻塞每一個acquire(),然後再獲取該許可,每個release() 添加一個許可,從而可能釋放一個正在阻塞的獲取者。 在線程池內創建線程並運行時,每個線程必須從信號量獲取許可,從而保證可以使用該項。該線程結束後,線程返回到池中並將許可返回到該信號量,從而允許其他線程獲取該項。注意,調用acquire() 時無法保持同步鎖定,因爲這會阻止線程返回到池中。

作用:

主要用於一次可以允許多少個線程併發執行

方法

 void acquire()

          從此信號量獲取一個許可,在提供一個許可前一直將線程阻塞,否則線程被中斷。

 void acquire(int permits)

          從此信號量獲取給定數目的許可,在提供這些許可前一直將線程阻塞,或者線程已被中斷。

 void acquireUninterruptibly()

          從此信號量中獲取許可,在有可用的許可前將其阻塞。

 void acquireUninterruptibly(int permits)

          從此信號量獲取給定數目的許可,在提供這些許可前一直將線程阻塞。

 int availablePermits()

          返回此信號量中當前可用的許可數。

 int drainPermits()

          獲取並返回立即可用的所有許可。

protected  Collection<Thread> getQueuedThreads()

          返回一個 collection,包含可能等待獲取的線程。

 int getQueueLength()

          返回正在等待獲取的線程的估計數目。

 boolean hasQueuedThreads()

          查詢是否有線程正在等待獲取。

 boolean isFair()

          如果此信號量的公平設置爲 true,則返回 true。

protected  void reducePermits(int reduction)

          根據指定的縮減量減小可用許可的數目。

 void release()

          釋放一個許可,將其返回給信號量。

 void release(int permits)

          釋放給定數目的許可,將其返回到信號量。

 String toString()

          返回標識此信號量的字符串,以及信號量的狀態。

 boolean tryAcquire()

          僅在調用時此信號量存在一個可用許可,才從信號量獲取許可。

 boolean tryAcquire(int permits)

          僅在調用時此信號量中有給定數目的許可時,才從此信號量中獲取這些許可。

 boolean tryAcquire(int permits, long timeout, TimeUnit unit)

          如果在給定的等待時間內此信號量有可用的所有許可,並且當前線程未被中斷,則從此信號量獲取給定數目的許可。

 boolean tryAcquire(long timeout, TimeUnit unit)

          如果在給定的等待時間內,此信號量有可用的許可並且當前線程未被中斷,則從此信號量獲取一個許可。

案例

public class Car extends Thread{
  private Driver driver;

  public Car(Driver driver) {
    super();
    this.driver = driver;
  }

  public void run() {
    driver.driveCar();
  }
}


 public class Run {
    public static void main(String[] args) {
      Driver driver = new Driver();
      for (int i = 0; i < 5; i++) {
        (new Car(driver)).start();
      }
    }
  }


public class Driver {
  // 將信號量設爲3
  private Semaphore semaphore = new Semaphore(3);

  public void driveCar() {
    try {
      semaphore.acquire();
      System.out.println(Thread.currentThread().getName() + " start at " + System.currentTimeMillis());
      Thread.sleep(1000);
      System.out.println(Thread.currentThread().getName() + " stop at " + System.currentTimeMillis());
      semaphore.release();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

輸出:

Thread-0 start at 1482665412515
Thread-3 start at 1482665412517
Thread-1 start at 1482665412517
Thread-3 stop at 1482665413517
Thread-0 stop at 1482665413517
Thread-4 start at 1482665413517
Thread-2 start at 1482665413517
Thread-1 stop at 1482665413518
Thread-4 stop at 1482665414517
Thread-2 stop at 1482665414517

Interruptstop

中斷(Interrupt)一個線程意味着在該線程完成任務之前停止其正在進行的一切,有效地中止其當前的操作,Thread.interrupt()方法不會中斷一個正在運行的線程(終止阻塞的線程)。這一方法實際上完成的是,在線程受到阻塞時拋出一箇中斷信號,這樣線程就得以退出阻塞的狀態。更確切的說,如果線程被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞,那麼,它將接收到一箇中斷異常(InterruptedException),從而提早地終結被阻塞狀態。 

首先,忘掉Thread.stop方法。雖然它確實停止了一個正在運行的線程,然而,這種方法是不安全也是不受提倡的,這意味着,在未來的Java版本中,它將不復存在。 

  • isInterrupted(),用來判斷當前線程的中斷狀態(true or false)。
  • interrupted()是個Thread的static方法,用來恢復中斷狀態,名字起得額

理解

首先說interrupt, 它沒有stop那麼的粗暴,因爲可以用catch捕捉到InterruptedException這個異常

public class interrupt {
    public static void main(String[] args){
      MyThread mythread =new MyThread();
      mythread.start();
      try{
        Thread.sleep(10000);
      }catch(InterruptedException e){
      }
      mythread.interrupt();
      //mythread.flag=false;
   }
  }
  class MyThread extends Thread{
    public boolean flag =true;
    public void run(){
      while(true){
       System.out.println(new Date());
        try{
          sleep(1000);
        }catch(InterruptedException e){
          System.out.println("Oh,no!!");
          return;
        }
      }
  

輸出如下:

Thu Apr 03 20:36:11 CST 2014
Thu Apr 03 20:36:12 CST 2014
Thu Apr 03 20:36:13 CST 2014
Thu Apr 03 20:36:14 CST 2014
Thu Apr 03 20:36:15 CST 2014
Thu Apr 03 20:36:16 CST 2014
Thu Apr 03 20:36:17 CST 2014
Thu Apr 03 20:36:18 CST 2014
Thu Apr 03 20:36:19 CST 2014
Thu Apr 03 20:36:20 CST 2014

Oh,no!!

如果使用stop方法,則更加粗暴一些:

Thu Apr 03 20:49:10 CST 2014
Thu Apr 03 20:49:11 CST 2014
Thu Apr 03 20:49:12 CST 2014
Thu Apr 03 20:49:13 CST 2014
Thu Apr 03 20:49:14 CST 2014
Thu Apr 03 20:49:15 CST 2014

因爲此時線程直接終止,沒有catch異常的機會, 無法對線程結束這一行爲作出任何補救動作。

無論是interrupt還是stop都是不安全的做法,因爲如果我們在線程進行時打開了某些資源,那麼這樣粗暴的結束資源將無法正確關閉

正確停止線程

用一個共享變量標誌flag來控制線程的結束


public class interrupt {
  public static void main(String[] args){
    MyThread mythread =new MyThread();
    mythread.start();
    try{
      Thread.sleep(10000);
    }catch(InterruptedException e){
    }
    //mythread.stop();
    mythread.flag=false;
  }
}
class MyThread extends Thread{
  public boolean flag =true;
  public void run(){
    while(flag){
      System.out.println(new Date());
      try{
        sleep(1000);
      }catch(InterruptedException e){
        System.out.println("Oh,no!!");
        return;
      }
    }



線程池

線程池的好處

a. 每次new Thread新建對象性能差。
b. 線程缺乏統一管理,可能無限制新建線程,相互之間競爭,及可能佔用過多系統資源導致死機或oom。
c. 缺乏更多功能,如定時執行、定期執行、線程中斷。
相比new Thread,Java提供的四種線程池的好處在於:
a. 重用存在的線程,減少對象創建、消亡的開銷,性能佳。
b. 可有效控制最大併發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞。
c. 提供定時執行、定期執行、單線程、併發數控制等功能

線程池的功能

第一:降低資源消耗。通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。 
第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。 
第三:提高線程的可管理性。 

常用線程池

Java通過Executors提供四種線程池,分別爲:
newCachedThreadPool創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。

只能活躍60s
newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。
newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。

使用方法

ExecutorService    ThreadPool = Executors.newCachedThreadPool(); 

          threadPool.execute(th);

th爲自己寫好的線程,線程的啓動在線程池中執行

ExecutorService介紹

ExecutorService是Java中對線程池定義的一個接口,它java.util.concurrent包中,在這個接口中定義了和後臺任務執行相關的方法:

ExecutorService的創建

創建一個什麼樣的ExecutorService的實例(即線程池)需要g根據具體應用場景而定,不過Java給我們提供了一個Executors工廠類,它可以幫助我們很方便的創建各種類型ExecutorService線程池,Executors一共可以創建下面這四類線程池:

1. newCachedThreadPool 創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。

2. newFixedThreadPool 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。

3. newScheduledThreadPool 創建一個定長線程池,支持定時及週期性任務執行。

4. newSingleThreadExecutor 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所

ExecutorService的使用

ExecutorService executorService = Executors.newFixedThreadPool(10);

executorService.execute(new Runnable() {

public void run() {

    System.out.println("Asynchronous task");

});

executorService.shutdown();//關閉ExecutorService

ExecutorService的執行

ExecutorService有如下幾個執行方法:

- execute(Runnable)

- submit(Runnable)

- submit(Callable)

- invokeAny(...)

- invokeAll(...)

ExecutorService的關閉

當我們使用完成ExecutorService之後應該關閉它,否則它裏面的線程會一直處於運行狀態。如果要關閉ExecutorService中執行的線程,我們可以調用ExecutorService.shutdown()方法。在調用shutdown()方法之後,ExecutorService不會立即關閉,但是它不再接收新的任務,直到當前所有線程執行完成纔會關閉,所有在shutdown()執行之前提交的任務都會被執行。

如果我們想立即關閉ExecutorService,我們可以調用ExecutorService.shutdownNow()方法。這個動作將跳過所有正在執行的任務和被提交還沒有執行的任務。但是它並不對正在執行的任務做任何保證,有可能它們都會停止,也有可能執行完成

Java線程池中submit() 和 execute()方法有什麼區別?

兩個方法都可以向線程池提交任務,execute()方法的返回類型是void,它定義在Executor接口中, 而submit()方法可以返回持有計算結果的Future對象,它定義在ExecutorService接口中,它擴展了Executor接口,其它線程池類像ThreadPoolExecutor和ScheduledThreadPoolExecutor都有這些方法

線程池的種類,區別和使用場景

newCachedThreadPool

  • 底層:返回ThreadPoolExecutor實例,corePoolSize爲0;maximumPoolSize爲Integer.MAX_VALUE;keepAliveTime爲60L;unit爲TimeUnit.SECONDS;workQueue爲SynchronousQueue(同步隊列)
  • 通俗創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。線程池爲無限大,當執行第二個任務時第一個任務已經完成,會複用執行第一個任務的線程,而不用每次新建線程。
  • 適用:執行很多短期異步的小程序或者負載較輕的服務器

newFixedThreadPool

  • 底層:返回ThreadPoolExecutor實例,接收參數爲所設定線程數量nThread,corePoolSize爲nThread,maximumPoolSize爲nThread;keepAliveTime爲0L(不限時);unit爲:TimeUnit.MILLISECONDS;WorkQueue爲:new LinkedBlockingQueue<Runnable>()無解阻塞隊列
  • 通俗:創建可容納固定數量線程的池子,每個線程的存活時間是無限的,當池子滿了就不在添加線程了;如果池中的所有線程均在繁忙狀態,對於新任務會進入阻塞隊列中(無界的阻塞隊列)。
  • 適用:執行長期的任務,性能好很多

newSingleThreadExecutor:

  • 底層:FinalizableDelegatedExecutorService包裝的ThreadPoolExecutor實例,corePoolSize爲1;maximumPoolSize爲1;keepAliveTime爲0L;unit爲:TimeUnit.MILLISECONDS;workQueue爲:new LinkedBlockingQueue<Runnable>() 無解阻塞隊列
  • 通俗:創建只有一個線程的線程池,且線程的存活時間是無限的;當該線程正繁忙時,對於新任務會進入阻塞隊列中(無界的阻塞隊列)
  • 適用:一個任務一個任務執行的場景

NewScheduledThreadPool:

  • 底層:創建ScheduledThreadPoolExecutor實例,corePoolSize爲傳遞來的參數,maximumPoolSize爲Integer.MAX_VALUE;keepAliveTime爲0;unit爲:TimeUnit.NANOSECONDS;workQueue爲:new DelayedWorkQueue() 一個按超時時間升序排序的隊列
  • 通俗:創建一個固定大小的線程池,線程池內線程存活時間無限制,線程池可以支持定時及週期性任務執行(比如每個線程每隔多少秒執行一次),如果所有線程均處於繁忙狀態,對於新任務會進入DelayedWorkQueue隊列中,這是一種按照超時時間排序的隊列結構
  • 適用:週期性執行任務的場景

線程池任務執行流程:

  1. 當線程池小於corePoolSize時,新提交任務將創建一個新線程執行任務,即使此時線程池中存在空閒線程。
  2. 當線程池達到corePoolSize時,新提交任務將被放入workQueue中,等待線程池中任務調度執行
  3. 當workQueue已滿,且maximumPoolSize>corePoolSize時,新提交任務會創建新線程執行任務
  4. 當提交任務數超過maximumPoolSize時,新提交任務由RejectedExecutionHandler處理
  5. 當線程池中超過corePoolSize線程,空閒時間達到keepAliveTime時,關閉空閒線程
  6. 當設置allowCoreThreadTimeOut(true)時,線程池中corePoolSize線程空閒時間達到keepAliveTime也將關閉

CountDownLatch

CountDownLatch是什麼

CountDownLatch是在java1.5被引入的,跟它一起被引入的併發工具類還有CyclicBarrier、Semaphore、ConcurrentHashMapBlockingQueue,它們都存在於java.util.concurrent包下。CountDownLatch這個類能夠使一個線程等待其他線程完成各自的工作後再執行。例如,應用程序的主線程希望在負責啓動框架服務的線程已經啓動所有的框架服務之後再執行。

CountDownLatch是通過一個計數器來實現的,計數器的初始值爲線程的數量。每當一個線程完成了自己的任務後,計數器的值就會減1。當計數器值到達0時,它表示所有的線程已經完成了任務,然後在閉鎖上等待的線程就可以恢復執行任務。

CountDownLatch如何工作

CountDownLatch.java類中定義的構造函數:

count.public void CountDownLatch(int count) {...}

與CountDownLatch的第一次交互是主線程等待其他線程。主線程必須在啓動其他線程後立即調用CountDownLatch.await()方法。這樣主線程的操作就會在這個方法上阻塞,直到其他線程完成各自的任務。構造器中的計數值(count)實際上就是閉鎖需要等待的線程數量。這個值只能被設置一次,而且CountDownLatch沒有提供任何機制去重新設置這個計數值

其他N 個線程必須引用閉鎖對象,因爲他們需要通知CountDownLatch對象,他們已經完成了各自的任務。這種通知機制是通過 CountDownLatch.countDown()方法來完成的;每調用一次這個方法,在構造函數中初始化的count值就減1。所以當N個線程都調 用了這個方法,count的值等於0,然後主線程就能通過await()方法,恢復執行自己的任務。

在實時系統中的使用場景

讓我們嘗試羅列出在java實時系統中CountDownLatch都有哪些使用場景。我所羅列的都是我所能想到的。如果你有別的可能的使用方法,請在留言裏列出來,這樣會幫助到大家。

實現最大的並行性:有時我們想同時啓動多個線程,實現最大程度的並行性。例如,我們想測試一個單例類。如果我們創建一個初始計數爲1的CountDownLatch,並讓所有線程都在這個鎖上等待,那麼我們可以很輕鬆地完成測試。我們只需調用 一次countDown()方法就可以讓所有的等待線程同時恢復執行。

開始執行前等待n個線程完成各自任務:例如應用程序啓動類要確保在處理用戶請求前,所有N個外部系統已經啓動和運行了。

3死鎖檢測:一個非常方便的使用場景是,你可以使用n個線程訪問共享資源,在每次測試階段的線程數目是不同的,並嘗試產生死鎖。

使用步驟

第一創建 CountDownLatch對象,並設置等待的線程個數

 CountDownLatch  sCountDownLatch = new CountDownLatch(THREAD_NUMBER); 

在每個要執行的線程的run方法中,添加

​​​​​​​latch.countDown();

第三步: 在等待線程的前面添加

 sCountDownLatch.await(); 

案例

public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3);
   new Thread(new Runnable() {
@Override
public void run() {
 System.out.println("我是線程1");
 latch.countDown();

}
}) {

   }.start();

   new Thread(new Runnable() {

   @Override
   public void run() {
   System.out.println("我是線程2");
   latch.countDown();//不可少

   }
   }) {

   }.start();

   new Thread(new Runnable() {
     @Override
   public void run() {
   System.out.println("我是線程3");
   latch.countDown();

   }
   }) {
   }.start();
   try {

latch.await();
System.out.println("我是主線程");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();

}

 CyclicBarrier

CyclicBarrier是什麼

CyclicBarrier是一個同步的輔助類,可循環利用的屏障允許一組線程相互之間等待,達到一個共同點,再繼續執行。可看成是個障礙,所有的線程必須到齊後才能一起通過這個障礙

CyclicBarrier如何使用

介紹CyclicBarrier的兩個構造函數:CyclicBarrier(int parties)和CyclicBarrier(int parties, Runnable barrierAction) :前者只需要聲明需要攔截的線程數即可,而後者還需要定義一個等待所有線程到達屏障優先執行的Runnable對象。

public class CyclicBarrierDemo {
    private static final ThreadPoolExecutor threadPool=new 
   ThreadPoolExecutor(4,10,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
    //當攔截線程數達到4時,便優先執行barrierAction,然後再執行被攔截的線程。
    private static final CyclicBarrier cb=new CyclicBarrier(4,new Runnable() {
      public void run()
      {
        System.out.println("寢室四兄弟一起出發去球場");
      }
    });
    private static class GoThread extends Thread{
      private final String name;
      public GoThread(String name)
      {
        this.name=name;
      }
      public void run()
      {
        System.out.println(name+"開始從宿舍出發");
        try {
          Thread.sleep(1000);
          cb.await();//攔截線程
          System.out.println(name+"從樓底下出發");
          Thread.sleep(1000);
          System.out.println(name+"到達操場");

        }
        catch(InterruptedException e)
        {
          e.printStackTrace();
        }
        catch(BrokenBarrierException e)
        {
          e.printStackTrace();
        }
      }
    }
    public static void main(String[] args) {
      // TODO Auto-generated method stub
      String[] str= {"李明","王強","劉凱","趙傑"};
      for(int i=0;i<4;i++)
      {
        threadPool.execute(new GoThread(str[i]));
      }
      try
      {
        Thread.sleep(4000);
        System.out.println("四個人一起到達球場,現在開始打球");
      }
      catch(InterruptedException e)
      {
        e.printStackTrace();
      }
    }

結果

李明開始從宿舍出發
趙傑開始從宿舍出發
王強開始從宿舍出發
劉凱開始從宿舍出發
寢室四兄弟一起出發去球場
趙傑從樓底下出發
李明從樓底下出發
劉凱從樓底下出發
王強從樓底下出發
趙傑到達操場
王強到達操場
李明到達操場
劉凱到達操場
四個人一起到達球場,現在開始打球

CountDownLatchCyclicBarrier區別

CyclicBarrier可以將一組線程停留在某一個狀態下,而CountDownLatch則不可以。而且CyclicBarrier可以循環使用,CountDownLatch不行。

如何保證線程順序執行

方法一:通過共享對象鎖加上可見變量來實現。

類似下面這種寫法

private volatile int orderNum = 1;      
public synchronized void methodA() {  
try {  
while (orderNum != 1) {  
wait();  
}  
for (int i = 0; i < 2; i++) {  
System.out.println("AAAAA");  
}  
orderNum = 2;  
notifyAll();  
} catch (InterruptedException e) {  
e.printStackTrace();  
}  
}  
 public synchronized void methodB() {  
        try {  
            while (orderNum != 2) {  
                wait();  
            }  
            for (int i = 0; i < 2; i++) {  
                System.out.println("BBBBB");  
            }  
            orderNum = 3;  
            notifyAll();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  
  
    public synchronized void methodC() {  
        try {  
            while (orderNum != 3) {  
                wait();  
            }  
            for (int i = 0; i < 2; i++) {  
                System.out.println("CCCCC");  
            }  
            orderNum = 1;  
            notifyAll();  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
    }  

方法二:通過主線程Join()

public class Test2 {  
public static void main(String[] args) throws InterruptedException {  
T11 t1 = new T11();  
T22 t2 = new T22();  
T33 t3 = new T33();  
t1.start();  
t1.join();  
t2.start();  
t2.join();  
t3.start();  
}  
}  

方法三:通過線程執行時Join()

Thread t2=new Thread(new Runnable() {
			public void run() {
				try {
					t1.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("我是線程二");
			}

Lock 與Synchronized的區別

主要目的是和synchronized一樣, 兩者都是爲了解決同步問題,處理資源爭端而產生的技術。功能類似但有一些區別。

區別如下:

1、synchronized內置關鍵字,在JVM層面實現,發生異常時,會自動釋放線程佔有的鎖,因此不會導致死鎖現象發生。Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖

2、Lock具有高級特性:時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變量或者鎖投票,可以知道有沒有成功獲取鎖

3、當競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized

4·lock更靈活,可以自由定義多把鎖的枷鎖解鎖順序(synchronized要按照先加的後解順序)

提供多種加鎖方案,lock 阻塞式, trylock 無阻塞式, lockInterruptily 可打斷式, 還有trylock的帶超時時間版本。

本質上和監視器鎖(即synchronized是一樣的)

結論:建議用 synchronized 開發,直到確實證明 synchronized 不合適,而不要僅僅是假設如果使用 ReentrantLock “性能會更好”。

Delay Quene

簡介

是一個無界的BlockingQueue,用於放置實現了Delayed接口的對象,其中的對象只能在其到期時才能從隊列中取走。這種隊列是有序的,即隊頭對象的延遲到期時間最長。注意:不能將null元素放置到這種隊列中。

Delayed

一種混合風格的接口,用來標記那些應該在給定延遲時間之後執行的對象。此接口的實現必須定義一個 compareTo 方法,該方法提供與此接口的 getDelay 方法一致的排序

延時隊列的實現

簡單的延時隊列要有三部分:第一實現了Delayed接口的消息體第二消費消息的消費者第三存放消息的延時隊列,那下面就來看看延時隊列demo。

消息體定義 實現Delayed接口就是實現兩個方法即compareTo 和 getDelay最重要的就是getDelay方法,這個方法用來判斷是否到期

1.消息體

public class Message implements Delayed {  
    private int id;  
    private String body; // 消息內容  
    private long excuteTime;// 延遲時長,這個是必須的屬性因爲要按照這個判斷延時時長。  
  
    public int getId() {  
        return id;  
    }  
  
    public String getBody() {  
        return body;  
    }  
  
    public long getExcuteTime() {  
        return excuteTime;  
    }  
  
    public Message(int id, String body, long delayTime) {  
        this.id = id;  
        this.body = body;  
        this.excuteTime = TimeUnit.NANOSECONDS.convert(delayTime, TimeUnit.MILLISECONDS) + System.nanoTime();  
    }  
  
    // 自定義實現比較方法返回 1 0 -1三個參數  
    @Override  
    public int compareTo(Delayed delayed) {  
        Message msg = (Message) delayed;  
        return Integer.valueOf(this.id) > Integer.valueOf(msg.id) ? 1  
                : (Integer.valueOf(this.id) < Integer.valueOf(msg.id) ? -1 : 0);  
    }  
  
    // 延遲任務是否到時就是按照這個方法判斷如果返回的是負數則說明到期否則還沒到期  
    @Override  
    public long getDelay(TimeUnit unit) {  
        return unit.convert(this.excuteTime - System.nanoTime(), TimeUnit.NANOSECONDS);  
    }  
}  

2.消費者

public class Consumer implements Runnable {  
    // 延時隊列 ,消費者從其中獲取消息進行消費  
    private DelayQueue<Message> queue;  
  
    public Consumer(DelayQueue<Message> queue) {  
        this.queue = queue;  
    }  
    @Override  
    public void run() {  
        while (true) {  
            try {  
                Message take = queue.take();  
                System.out.println("消費消息id:" + take.getId() + " 消息體:" + take.getBody());  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
        }  
    }  
}  

3.延時隊列

public class DelayQueueTest {  
     public static void main(String[] args) {    
            // 創建延時隊列    
            DelayQueue<Message> queue = new DelayQueue<Message>();    
            // 添加延時消息,m1 延時3s    
            Message m1 = new Message(1, "world", 3000);    
            // 添加延時消息,m2 延時10s    
            Message m2 = new Message(2, "hello", 10000);    
            //將延時消息放到延時隊列中  
            queue.offer(m2);    
            queue.offer(m1);    
            // 啓動消費線程 消費添加到延時隊列中的消息,前提是任務到了延期時間   
            ExecutorService exec = Executors.newFixedThreadPool(1);  
            exec.execute(new Consumer(queue));  
            exec.shutdown();  
        }    
}  

將消息體放入延遲隊列中,在啓動消費者線程去消費延遲隊列中的消息,如果延遲隊列中的消息到了延遲時間則可以從中取出消息否則無法取出消息也就無法消費,這就是延遲隊列demo。

參考文件:https://www.cnblogs.com/shamo89/p/7055039.html

Exchanger

 1、用於實現兩個對象之間的數據交換,每個對象在完成一定的事務後想與對方交換數據,第一個先拿出數據的對象將一直等待第二個對象拿着數據

          到來時,彼此才能交換數據。

  2、方法:exchange(V x)

          等待另一個線程到達此交換點(除非當前線程被中斷),然後將給定的對象傳送給該線程,並接收該線程的對象。

  3、應用:使用 Exchanger 在線程間交換緩衝區

案例


public class ExchangeTest {
	public static void main(String[] args) {
		ExecutorService service =Executors.newCachedThreadPool();
		final Exchanger exchanger = new Exchanger();
		service.execute(new Runnable() {
			@Override
			public void run() {
				try{
					String data1 = "零食";
					System.out.println("線程"+Thread.currentThread().getName()+
							"正在把數據 "+data1+" 換出去");
					Thread.sleep((long)Math.random()*10000);
					String data2 = (String)exchanger.exchange(data1);
					System.out.println("線程 "+Thread.currentThread().getName()+
							"換回的數據爲 "+data2);
				}catch(Exception e){
					e.printStackTrace();
				}
				
			}
		});
		
		service.execute(new Runnable() {
			
			@Override
			public void run() {
				try{
					String data1 = "錢";
					System.out.println("線程"+Thread.currentThread().getName()+
							"正在把數據 "+data1+" 交換出去");
					Thread.sleep((long)(Math.random()*10000));
					String data2 =(String)exchanger.exchange(data1);
					System.out.println("線程 "+Thread.currentThread().getName()+
							"交換回來的數據是: "+data2);
				}catch(Exception e){
					e.printStackTrace();
				}
				
				
			}
		});
	}
}

輸出結果:

線程pool-1-thread-1正在把數據 零食 換出去
線程pool-1-thread-2正在把數據 錢 交換出去
線程 pool-1-thread-1換回的數據爲 錢
線程 pool-1-thread-2交換回來的數據是: 零食

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章