Java并發編程

salt 8年前發布 | 11K 次閱讀 并發 Java Java開發

并發一定比串行快么?

這個問題肯定是錯的,并發比串行慢的原因在于: 線程有創建和上下文切換的開銷

上下文切換

即使是單核處理器也支持多線程執行代碼,CPU通過給每個線程分配CPU時間片來實現這個機制。CPU通過時間片分配的算法來循環執行任務,當前任務執行一個時間片后會切換到下一個任務。但是,在切換前會保持上一個任務的狀態,以便下次切換回這個任務時,可以再加之這個任務的狀態。所以 任務從保存到再加載的過程就是一次上下文切換

如何減少上下文切換

  • 無鎖并發編程:多線程競爭鎖,會引起上下文切換,所以多線程處理數據時,可以用一些辦法避免使用鎖,如將數據的ID按照Hash算法取模分段,不同的線程處理不同段的數據
  • CAS算法:Java的Atomic包使用CAS算法來更新數據,而不需要加鎖。
  • 使用最少線程:避免創建不需要的線程,比如任務很少,但是創建了很多線程來處理,這樣會造成大量線程處于等待
  • 協程:在單行線程中實現多任務調度,并在單線程中維持多個任務的切換

避免死鎖的幾種方式

  • 避免一個線程同時獲取多個鎖
  • 避免一個線程在鎖內同時占用多個資源,盡量保證每個鎖只占用一個資源
  • 嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內部鎖機制。
  • 對于數據庫鎖,加鎖和解鎖必須在一個數據庫連接中里,否則會出現解鎖失敗的情況。

資源限制

資源限制指的是程序的執行速度受限于計算機硬件資源或軟件資源,如服務器的帶寬只有2Mb/s,某個資源的下載速度為1Mb/s,系統啟動10個線程去下載資源,下載速度不會變成10Mb/s,所以在進行并發的時候回考慮資源的限制。硬件資源限制有帶寬的上傳/下載速度、硬盤的讀寫速度和CPU的處理速度。軟件資源限制有數據庫的連接數和socket連接數等。

資源限制引來的問題:為了將代碼執行速度加快將代碼中串行執行的部分變成并發執行,因為資源受限,仍然在串行執行,這時候程序不僅不會加快,反而會變慢,因為增加了上下文切換和資源調度的時間。

如何解決資源限制問題:可以使用集群并行執行程序,既然單機的資源有限,那么可以讓程序在多機上運行,比如使用ODPS、Hadoop或者自己搭個服務器集群,不同的機器處理不同的數據,可以通過“數據ID%機器數”,計算得到一個機器編號,然后由對應編號的機器處理這個數據,對于軟件資源受限,可以使用資源池來復用如使用連接池將數據庫和Socket連接復用,或者在調用對方webservice接口獲取數據只建立一個連接。

Java并發機制的底層實現原理

Java代碼在編譯后會變成Java字節碼,字節碼被類加載器加載到JVM里,JVM執行字節碼,最終需要轉化為匯編指令在CPU上執行,Java所使用的并發機制依賴于JVM的實現和CPU的指令

volatile的應用

volatile是輕量級的synchronized,在多處理器并發中保證了共享變量的 可見性 ,可見性是指當一個線程修改了一個共享變量,另一個線程能讀到修改的值, 它不會引起線程上下文切換和調度

volatile在java代碼轉換為匯編代碼 會多了一個Lock前綴的指令,在多核處理器下發生兩件事情

  • 將當前處理器緩存行的數據寫回到系統內存
  • 將這個協會內存的操作會使得其他CPU力緩存了該內存的地址的數據無效

為了提高處理速度,處理器不直接和內存通信,而是將系統內存的數據讀到內部緩存(L1,L2或其他)后再進行操作,但操作完不知道何時回寫到內存,如果聲明了volatile的變量進行寫操作,JVM就會向處理器發送一條Lock前綴的指令,將這個變量所在緩存行的數據寫回到系統內存,但是就是寫會內存,如果其他處理器緩存的值還是舊的,再執行計算操作就會有問題,所以在多處理器下為了保證各個處理器的緩存是一致,就會執行緩存一致性協議,每個處理器通過嗅探在總線上傳播的數據來檢查自己的緩存值是否過期,當處理器發現自己緩存行所對應的內存地址被修改,就會將當前處理器緩存行設置為無效,當處理器對數據進行修改操作,會重新從系統內存讀到處理器緩存中

synchronized的和應用

javase1.6 對synchronized進行各種優化,過去被人稱為重量級鎖。

java每個對象都是鎖

  • 對于普通同步方法,鎖就是實例對象
  • 對于靜態同步方法,鎖就是當前類的Class對象
  • 對于同步代碼塊,鎖就是Synchonized括號里配置的對象

JVM基于進入和退出Monitor對象來實現方法同步和代碼塊同步

synchronized用的鎖是存在Java對象頭里的。Java對象頭里的Mark Word力默認儲存對象的HashCode,分代年齡和鎖標記位。 在運行期間,MarkWord儲存的數據會隨著鎖標記位的變化而變化

Javase1.6中 鎖一共有4種狀態,級別從低到高為:無鎖狀態、偏向鎖狀態、輕量級鎖狀態和重量級鎖狀態,這幾個狀態會隨著競爭情況逐漸升級。 鎖可以升級但不能降級 ,目的是為了提高獲得鎖和釋放鎖的效率

  1. 偏向鎖:大多數情況下,鎖不僅不存在多線程競爭,而且總是同一個線程多次獲取,為了讓線程獲得鎖的代價更低引入偏向鎖。當某一線程訪問同步塊時,會在對象頭和棧幀中的瑣記錄里存儲鎖偏向的線程ID,以后該線程在進入該同步塊的時候,不需要再次使用CAS原子操作進行加鎖和解鎖,只需要簡單的測試一下對象頭中的Mark Word是否存在指向當前線程的偏向鎖。如果測試成功,則表示獲得鎖,否則檢測是否設置有偏向鎖,如果沒有,則使用CAS競爭鎖,否則偏向鎖指向該線程。
  2. 輕量級鎖:線程執行同步塊之前,會在線程私有的棧幀中開辟用于存儲鎖記錄的空間,稱為Displaced Mark Word。然后線程嘗試將對象Mark Word的替換為指向Displaced Mark Word記錄的指針,如果成功,那么當前線程獲得鎖,如果失敗,那么使用自旋獲得鎖。

原子操作的實現原理

  1. 處理器實現原子操作:使用總線鎖保證原子性,使用緩存鎖保證原子性(修改內存地址,緩存一致性機制:阻止同時修改由2個以上的處理器緩存的內存區域數據)
  2. JAVA實現原子操作:循環使用CAS實現原子操作

    public class Counter {
     private AtomicInteger atomicI = new AtomicInteger(0);
     private int i = 0;
     public static void main(String[] args) {
         final Counter cas = new Counter();
         List<Thread> ts = new ArrayList<Thread>(600);
         long start = System.currentTimeMillis();
         for (int j = 0; j < 100; j++) {
             Thread t = new Thread(new Runnable() {
                 @Override
                 public void run() {
                     for (int i = 0; i < 10000; i++) {
                         cas.count();
                         cas.safeCount();
                     }
                 }
             });
             ts.add(t);
         }

     for (Thread t : ts) {
         t.start();
     }
     // 等待所有線程執行完成
     for (Thread t : ts) {
         try {
             t.join();
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
     }
    
     System.out.println(cas.i);
     System.out.println(cas.atomicI.get());
     System.out.println(System.currentTimeMillis() - start);
    
    

    }

    /**

    • 使用CAS實現線程安全計數器 */ private void safeCount() { for (;;) {
       int i = atomicI.get();
       boolean suc = atomicI.compareAndSet(i, ++i);
       if (suc) {
           break;
       }
      
      } } /**
    • 非線程安全計數器 */ private void count() { i++; } }</code></pre> </li> </ol>

      CAS實現原子操作的三大問題

      1. ABA問題:因為CAS需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是A,變成了B,又變成了A,那么使用CAS進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA問題的解決思路就是使用版本號。在變量前面追加上版本號,每次變量更新的時候把版本號加一,那么A-B-A 就會變成1A-2B-3A。
      2. 循環時間長開銷大。自旋CAS如果長時間不成功,會給CPU帶來非常大的執行開銷。如果JVM能支持處理器提供的pause指令那么效率會有一定的提升,pause指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline),使CPU不會消耗過多的執行資源,延遲的時間取決于具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出循環的時候因內存順序沖突(memory order violation)而引起CPU流水線被清空(CPU pipeline flush),從而提高CPU的執行效率。
      3. 只能保證一個共享變量的原子操作。當對一個共享變量執行操作時,我們可以使用循環CAS的方式來保證原子操作,但是對多個共享變量操作時,循環CAS就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變量合并成一個共享變量來操作。比如有兩個共享變量i=2,j=a,合并一下ij=2a,然后用CAS來操作ij。從Java1.5開始JDK提供了AtomicReference類來保證引用對象之間的原子性,你可以把多個變量放在一個對象里來進行CAS操作。

      Java內存模型

      double_check的問題

      public class DoubleCheckedlocking{
      private static Instance instance;
      public static Instance getInstance(){
       if(instance==null){
        synchronized(DoubleCheckedlocking.class){
         if(instance==null)

       instance=new Instance();
      

      } } } } //根據重排序可能會出現的問題 instance=new Instance()常見一個對象可以分成三步 memory=allocate(),//1.分配對象的內存空間 ctorInstance(memory)//2.初始化對象 instance=memory //3.設置Instance指向剛分配的內存地址 如果2,3 重排序顛倒后 if語句就可以是引用是上存在但是對象還未被初始化,所以 可以給Instance加上一個volatile因為內存屏障的緣故</code></pre>

      Java中的鎖

      鎖是用來控制多個線程訪問共享資源的方式,一般來說,一個鎖能夠防止多個線程同時訪問共享資源,但是有些鎖可以運行多個線程并發的訪問共享資源,比如讀寫鎖。Lock接口和synchronized可以通過獲取鎖和釋放鎖,但是前者比后者更具擴展性。

      Lock是一個接口,定義了鎖獲取和釋放的基本操作

      Lock和synchronized區別

      • Lock接口可以嘗試非阻塞地獲取鎖 當前線程嘗試獲取鎖。如果這一時刻鎖沒有被其他線程獲取到,則成功獲取并持有鎖。
      • Lock接口能被中斷地獲取鎖 與synchronized不同,獲取到鎖的線程能夠響應中斷,當獲取到的鎖的線程被中斷時,中斷異常將會被拋出,同時鎖會被釋放。
      • Lock接口在指定的截止時間之前獲取鎖,如果截止時間到了依舊無法獲取鎖,則返回。

      Lock接口的APi

      • void lock() 獲取鎖,調用該方法當前線程將會獲取鎖,當鎖獲取后,該方法將返回。
      • void lockInterruptibly() throws InterruptedException 可中斷獲取鎖,與lock()方法不同之處在于該方法會響應中斷,即在鎖的獲取過程中可以中斷當前線程
      • boolean tryLock() 嘗試非阻塞的獲取鎖,調用該方法立即返回,true表示獲取到鎖
      • boolean tryLock(long time,TimeUnit unit) throws InterruptedException 超時獲取鎖,以下情況會返回:時間內獲取到了鎖,時間內被中斷,時間到了沒有獲取到鎖。
      • void unlock() 釋放鎖

      隊列同步器(AQS)

      隊列同步器AbstractQueuedSynchronizer(以下簡稱同步器),是用來構建鎖或者其他同步組件的基礎框架, 它使用了一個int成員變量表示同步狀態,通過內置的FIFO隊列來完成資源獲取線程的排隊工作 。同步器的主要使用方式是繼承,子類通過繼承同步器并實現它的抽象方法來管理同步狀態,在抽象方法的實現過程中免不了要對同步狀態進行更改,這時就需要使用同步器提供的3個方法(getState()、setState(int newState)和compareAndSetState(int expect,int update))來進行操作,因為它們能夠保證狀態的改變是安全的。同步器既可以支持獨占式地獲取同步狀態,也可以支持共享式地獲取同步狀態,這樣就可以方便實現不同類型的同步組件(ReentrantLock、ReentrantReadWriteLock和CountDownLatch等)。同步器是實現鎖(也可以是任意同步組件)的關鍵,在鎖的實現中聚合同步器,利用同步器實現鎖的語義。可以這樣理解二者之間的關系:鎖是面向使用者的,它定義了使用者與鎖交互的接口(比如可以允許兩個線程并行訪問),隱藏了實現細節;同步器面向的是鎖的實現者,它簡化了鎖的實現方式,屏蔽了同步狀態管理、線程的排隊、等待與喚醒等底層操作。鎖和同步器很好地隔離了使用者和實現者所需關注的領域

      enter image description here

      enter image description here

      public class Mutex implements Lock {
      private static class Sync extends AbstractQueuedSynchronizer {

      @Override
      protected boolean isHeldExclusively() {
          return getState() == 1;
      }
      
      @Override
      protected boolean tryAcquire(int arg) {
          if (compareAndSetState(0, 1)) {
              setExclusiveOwnerThread(Thread.currentThread());
              return true;
          }
          return false;
      }
      
      @Override
      protected boolean tryRelease(int arg) {
          if (getState() == 0) throw new IllegalMonitorStateException();
          setExclusiveOwnerThread(null);
          setState(0);
          return true;
      }
      
      Condition newCondition() {
          return new ConditionObject();
      }
      

      }

      private final Sync sync = new Sync();

      @Override public void lock() {

      sync.acquire(1);
      

      }

      @Override public void lockInterruptibly() throws InterruptedException {

      sync.acquireInterruptibly(1);
      

      }

      @Override public boolean tryLock() {

      return sync.tryAcquire(1);
      

      }

      @Override public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {

      return sync.tryAcquireNanos(1, unit.toNanos(time));
      

      }

      @Override public void unlock() {

      sync.release(1);
      

      }

      @Override public Condition newCondition() {

      return sync.newCondition();
      

      }

    }</code></pre>

    以上是獨占式鎖是一個自定義的同步組件,在同一個時刻指運行一個線程占有鎖,用戶在使用Mutex并不會直接和內部同步器打交道,而是調用Mutex提供的方法,在Mutex的實現中,獲取鎖Lock方法。

    同步隊列

    同步器依賴內部的同步隊列(一個FIFO雙向隊列)來完成同步狀態的管理。同步隊列中的節點(Node)用來保存"獲取同步狀態失敗的線程"引用、等待狀態以及前驅和后繼節點。

    enter image description here

    enter image description here

    當前線程獲取同步狀態失敗時,同步器會將當前線程、等待狀態等信息構造成為一個節點(Node)并將其加入同步隊列,同時會”“阻塞”當前線程。當一個線程成功地獲取了同步狀態(或者鎖),其他線程將無法獲取到同步狀態,轉而被構造成為節點并加入到同步隊列中,而這個加入隊列的過程必須要保證線程安全。同步器提供了一個基于CAS的設置尾節點的方法:compareAndSetTail(Nodeexpect,Nodeupdate),它需要傳遞當前線程“認為”的尾節點和當前節點,只有設置成功后,當前節點才正式與之前的尾節點建立關聯。

    獨占式同步狀態獲取與釋放

    主要邏輯:首先調用自定義同步器實現的tryAcquire(int arg)方法,該方法保證線程安全的獲取同步狀態,如果同步狀態獲取失敗,則構造同步節點(獨占式Node.EXCLUSIVE,同一時刻只能有一個線程成功獲取同步狀態)并通過addWaiter(Node node)方法將該節點加入到同步隊列的尾部,最后調用acquireQueued(Node node,int arg)方法,使得該節點以“死循環”的方式獲取同步狀態

    enter image description here

    //將節點加入到同步隊列的尾部
    private Node addWaiter(Node mode) {
          Node node = new Node(Thread.currentThread(), mode);//“生成節點”
          // Try the fast path of enq; backup to full enq on failure
          //快速嘗試在尾部添加
          Node pred = tail;
          if (pred != null) {
              node.prev = pred;//先將當前節點node的前驅指向當前tail
              if (compareAndSetTail(pred, node)) {//CAS嘗試將tail設置為node
                  //如果CAS嘗試成功,就說明"設置當前節點node的前驅"與"CAS設置tail"之間沒有別的線程設置tail成功
                  //只需要將"之前的tail"的后繼節點指向node即可
                  pred.next = node;
                  return node;
              }
          }
          enq(node);//否則,通過死循環來保證節點的正確添加
          return node;
      }
    private Node enq(final Node node) {
        for (;;) {//通過死循環來保證節點的正確添加
            Node t = tail;
            if (t == null) { // Must initialize 同步隊列為空的情況
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {//直到CAS成功為止
                    t.next = node;
                    return t;//結束循環
                }
            }
        }
    }

    上述代碼通過使用compareAndSetTail(Node expect,Node update)來確保節點能夠被線程安全添加,如果使用普通的LinkedList來維護節點之間的關系,那么當一個線程獲取到同步狀態,而其他多個線程由于調用tryAcquire(int arg)方法獲取同步狀態失敗而并發被添加到LinkedList,LinkedList將難以保證Node的正確添加

    在enq(final Node node)方法中,同步器通過“死循環”來保證節點的正確添加,在“死循環”中只有通過CAS將節點設置成為尾節點之后,當前線程才能從該方法返回,否則,當前線程不斷地嘗試設置。可以看出,enq(final Node node)方法將并發添加節點的請求通過CAS變得“串行化”了。

    節點自旋

    節點進入同步隊列之后,就進入了一個自旋的過程,每個節點(或者說是線程)都在自省地觀察,當條件滿足,獲取到了同步狀態,就可以從這個自旋過程中退出,否則依舊留在這個自旋過程中。

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {//無限循環
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {//前驅節點是首節點且獲取到了同步狀態
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;//從自旋中退出
                }
                if (shouldParkAfterFailedAcquire(p, node) &&//判斷獲取同步狀態失敗后是否需要阻塞
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

    共享式同步狀態獲取與釋放

    共享式獲取與獨占式獲取最主要的區別在于同一時刻能否有多個線程同時獲取到同步狀態。

    以文件的讀寫為例,如果一個程序在對文件進行讀操作,那么這一時刻對于該文件的寫操作均被阻塞,而讀操作能夠同時進行。寫操作要求對資源的獨占式訪問,而讀操作可以是共享式訪問

    enter image description here

    調用同步器的acquireShared(int arg)方法可以共享式地獲取同步狀態。

    public final void acquireShared(int arg) {
            if (tryAcquireShared(arg) < 0)
                doAcquireShared(arg);
        }
          private void doAcquireShared(int arg) {
            final Node node = addWaiter(Node.SHARED);
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head) {
                        int r = tryAcquireShared(arg);
                        if (r >= 0) {
                            setHeadAndPropagate(node, r);
                            p.next = null; // help GC
                            if (interrupted)
                                selfInterrupt();
                            failed = false;
                            return;
                        }
                    }
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }

    在acquireShared(int arg)方法中,同步器調用tryAcquireShared(int arg)方法嘗試獲取同步狀態,tryAcquireShared(int arg)方法返回值為int類型,當返回值大于等于0時,表示能夠獲取到同步狀態。因此,在共享式獲取的自旋過程中,成功獲取到同步狀態并退出自旋的條件就是tryAcquireShared(int arg)方法返回值大于等于0。

    在doAcquireShared(int arg)方法的自旋過程中,如果當前節點的前驅為頭節點時,嘗試獲取同步狀態,如果返回值大于等于0,表示該次獲取同步狀態成功并從自旋過程中退出。

    重入鎖

    它表示該鎖能夠支持一個線程對資源的重復加鎖。除此之外,該鎖的還支持獲取鎖時的公平和非公平性選擇

    之前的例子,當一個線程調用Mutex的lock()方法獲取鎖之后,如果再次調用lock()方法,則該線程將會被自己所阻塞,原因是Mutex在實現tryAcquire(int acquires)方法時沒有考慮占有鎖的線程再次獲取鎖的場景,而在調用tryAcquire(int acquires)方法時返回了false,導致該線程被阻塞。簡單地說,Mutex是一個不支持重進入的鎖。 而synchronized關鍵字隱式的支持重進入, 比如一個synchronized修飾的遞歸方法,在方法執行時,執行線程在獲取了鎖之后仍能連續多次地獲得該鎖,而不像Mutex由于獲取了鎖,而在下一次獲取鎖時出現阻塞自己的情況。

    ReentrantLock雖然沒能像synchronized關鍵字一樣支持隱式的重進入,但是在調用lock()方法時,已經獲取到鎖的線程,能夠再次調用lock()方法獲取鎖而不被阻塞。

    公平鎖與非公平鎖的比較

    公平性鎖每次都是從同步隊列中的第一個節點獲取到鎖,而非公平性鎖出現了一個線程連續獲取鎖的情況。

    非公平性鎖可能使線程“饑餓”,當一個線程請求鎖時,只要獲取了同步狀態即成功獲取鎖。在這個前提下,剛釋放鎖的線程再次獲取同步狀態的幾率會非常大,使得其他線程只能在同步隊列中等待。

    為什么它又被設定成默認的實現呢?非公平性鎖模式下線程上下文切換的次數少,因此其性能開銷更小。公平性鎖保證了鎖的獲取按照FIFO原則,而代價是進行大量的線程切換。非公平性鎖雖然可能造成線程“饑餓”,但極少的線程切換,保證了其更大的吞吐量。

    讀寫鎖

    讀寫鎖在同一時刻可以允許多個讀線程訪問,但是在寫線程訪問時,所有的讀線程和其他寫線程均被阻塞。

    讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖,通過分離讀鎖和寫鎖,使得并發性相比一般的排他鎖有了很大提升。除了保證寫操作對讀操作的可見性以及并發性的提升之外,讀寫鎖能夠簡化讀寫交互場景的編程方式。在讀多于寫的情況下,讀寫鎖能夠提供比排它鎖更好的并發性和吞吐量。Java并發包提供讀寫鎖的實現是ReentrantReadWriteLock。

    ConcurrentHashMap的實現原理與使用

    ConcurrentHashMap是線程安全且高效的HashMap。

    為什么使用ConcurrentHashMap的原因

    1. HashMap線程不安全,在多線程下使用HashMap進行put操作會引起死循環,導致CPU利用率接近100%,原因在于多線程會導致HashMap的Entry鏈表形成環形數據結構,一旦形成環形數據結構,Entry的next節點永遠不為空,就回產生死循環獲取Entry
    2. HashTable效率低
    3. ConcurrentHashMap的鎖分段技術可以有效提升并發訪問率,原因在于HashTable在競爭中都是競爭同一把鎖,但是ConcurrentHashMap將數據分成一段一段地儲存,然后給每一段數據配一把鎖,當一個線程占用鎖訪問其中一段數據時候,其他段的數據也被其他線程訪問
      具體的實現及原理 http://www.ibm.com/developerworks/cn/java/java-lo-concurrenthashmap/

    Fork/Join框架

    Fork/Join框架是Java7提供了的一個用于并行執行任務的框架, 是一個把大任務分割成若干個小任務,最終匯總每個小任務結果后得到大任務結果的框架。

    enter image description here

    工作竊取算法

    指某個線程從其他隊列里竊取任務來執行。假如我們需要做一個比較大的任務,我們可以把這個任務分割為若干互不依賴的子任務,為了減少線程間的競爭,于是把這些子任務分別放到不同的隊列里,并為每個隊列創建一個單獨的線程來執行隊列里的任務,線程和隊列一一對應,比如A線程負責處理A隊列里的任務。但是有的線程會先把自己隊列里的任務干完,而其他線程對應的隊列里還有任務等待處理。干完活的線程與其等著,不如去幫其他線程干活,于是它就去其他線程的隊列里竊取一個任務來執行。而在這時它們會訪問同一個隊列,所以為了減少竊取任務線程和被竊取任務線程之間的競爭,通常會使用雙端隊列,被竊取任務線程永遠從雙端隊列的頭部拿任務執行,而竊取任務的線程永遠從雙端隊列的尾部拿任務執行。優點是充分利用線程進行并行計算,并減少了線程間的競爭。

     

     

    來自:http://www.jianshu.com/p/0256c2995cec

     

 本文由用戶 salt 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!