Java併發面試題

多線程

java中有幾種方法能夠實現一個線程?html

繼承Thread類;
實現Runnable接口;
實現Callable接口經過FutureTask包裝器來建立Thread線程;
使用ExecutorService、Callable、Future實現有返回結果的多線程(也就是使用了ExecutorService來管理前面的三種方式)。java

詳情參見:git

https://radiancel.github.io/2018/08/02/Multithreading/程序員

 

如何中止一個正在運行的線程?github

一、使用退出標誌,使線程正常退出,也就是當run方法完成後線程終止。web

二、使用stop方法強行終止,可是不推薦這個方法,由於stop和suspend及resume同樣都是過時做廢的方法。編程

三、使用interrupt方法中斷線程。api

參考:https://www.cnblogs.com/greta/p/5624839.html數組

notify()和notifyAll()有什麼區別?緩存

若是線程調用了對象的 wait()方法,那麼線程便會處於該對象的等待池中,等待池中的線程不會去競爭該對象的鎖。

當有線程調用了對象的 notifyAll()方法(喚醒全部 wait 線程)或 notify()方法(只隨機喚醒一個 wait 線程),被喚醒的的線程便會進入該對象的鎖池中,鎖池中的線程會去競爭該對象鎖。也就是說,調用了notify後只要一個線程會由等待池進入鎖池,而notifyAll會將該對象等待池內的全部線程移動到鎖池中,等待鎖競爭。

優先級高的線程競爭到對象鎖的機率大,倘若某線程沒有競爭到該對象鎖,它還會留在鎖池中,惟有線程再次調用 wait()方法,它纔會從新回到等待池中。而競爭到對象鎖的線程則繼續往下執行,直到執行完了 synchronized 代碼塊,它會釋放掉該對象鎖,這時鎖池中的線程會繼續競爭該對象鎖。

參考:

https://www.cnblogs.com/haoerlv/p/8648973.html

sleep()和 wait()有什麼區別?

對於sleep()方法,咱們首先要知道該方法是屬於Thread類中的。而wait()方法,則是屬於Object類中的。

sleep()方法致使了程序暫停執行指定的時間,讓出cpu該其餘線程,可是他的監控狀態依然保持者,當指定的時間到了又會自動恢復運行狀態。在調用sleep()方法的過程當中,線程不會釋放對象鎖。

當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象調用notify()方法後本線程才進入對象鎖定池準備,獲取對象鎖進入運行狀態。

參考:

https://www.cnblogs.com/hongten/p/hongten_java_sleep_wait.html

什麼是Daemon線程?它有什麼意義?

Java語言本身能夠建立兩種進程「用戶線程」和「守護線程」

用戶線程:就是咱們平時建立的普通線程.

守護線程:主要是用來服務用戶線程.

Daemon就是守護線程,他的意義是:

只要當前JVM實例中尚存在任何一個非守護線程沒有結束,守護線程就所有工做;只有當最後一個非守護線程結束時,守護線程隨着JVM一同結束工做。

Daemon的做用是爲其餘線程的運行提供便利服務,守護線程最典型的應用就是 GC (垃圾回收器),它就是一個很稱職的守護者。

參考:

https://www.cnblogs.com/ChrisWang/archive/2009/11/28/1612815.html

java如何實現多線程之間的通信和協做?

參考這篇:http://www.cnblogs.com/hapjin/p/5492619.html

什麼是可重入鎖(ReentrantLock)?

線程能夠進入任何一個它已經擁有的鎖所同步着的代碼塊。

代碼設計以下:

public class Lock{
    boolean isLocked = false;
    Thread  lockedBy = null;
    int lockedCount = 0;
    public synchronized void lock()
            throws InterruptedException{
        Thread thread = Thread.currentThread();
        while(isLocked && lockedBy != thread){
            wait();
        }
        isLocked = true;
        lockedCount++;
        lockedBy = thread;
    }
    public synchronized void unlock(){
        if(Thread.currentThread() == this.lockedBy){
            lockedCount--;
            if(lockedCount == 0){
                isLocked = false;
                notify();
            }
        }
    }
}

參考連接:https://www.cnblogs.com/dj3839/p/6580765.html

當一個線程進入某個對象的一個synchronized的實例方法後,其它線程是否可進入此對象的其它方法?

若是其餘方法前加了synchronized關鍵字,就不能,若是沒加synchronized,則可以進去。

若是這個方法內部調用了wait(),則能夠進入其餘加synchronized的方法。

若是其餘方法加了synchronized關鍵字,而且沒有調用wai方法,則不能。

synchronized和java.util.concurrent.locks.Lock的異同?

主要相同點:Lock能完成Synchronized所實現的全部功能。

主要不一樣點:Lock有比Synchronized更精確的線程予以和更好的性能。Synchronized會自動釋放鎖,可是Lock必定要求程序員手工釋放,而且必須在finally從句中釋放。

 

樂觀鎖和悲觀鎖的理解及如何實現,有哪些實現方式?

樂觀鎖是假設每次操做都不會衝突,如果遇到衝突失敗就重試直到成功;悲觀鎖是讓其餘線程都等待,等鎖釋放完了再競爭鎖。

樂觀鎖實現方式:cas,volatile

悲觀鎖實現方式:synchronized,Lock

併發框架

SynchronizedMap和ConcurrentHashMap有什麼區別?

SynchronizedMap()和Hashtable同樣,實現上在調用map全部方法時,都對整個map進行同步。而ConcurrentHashMap的實現卻更加精細,它對map中的全部桶加了鎖。因此,只要有一個線程訪問map,其餘線程就沒法進入map,而若是一個線程在訪問ConcurrentHashMap某個桶時,其餘線程,仍然能夠對map執行某些操做。

因此,ConcurrentHashMap在性能以及安全性方面,明顯比Collections.synchronizedMap()更加有優點。同時,同步操做精確控制到桶,這樣,即便在遍歷map時,若是其餘線程試圖對map進行數據修改,也不會拋出ConcurrentModificationException。

參考:

https://www.cnblogs.com/shamo89/p/6700353.html

 

CopyOnWriteArrayList能夠用於什麼應用場景?

CopyOnWriteArrayList的特性是針對讀操做,不作處理,和普通的ArrayList性能同樣。而在寫操做時,會先拷貝一份,實現新舊版本的分離,而後在拷貝的版本上進行修改操做,修改完後,將其更新至就版本中。

那麼他的使用場景就是:一個須要在多線程中操做,而且頻繁遍歷。其解決了因爲長時間鎖定整個數組致使的性能問題,解決方案即寫時拷貝。

另外須要注意的是CopyOnWrite容器只能保證數據的最終一致性,不能保證數據的實時一致性。因此若是你但願寫入的的數據,立刻能讀到,請不要使用CopyOnWrite容器。

參考:

https://www.cnblogs.com/duchanggang/p/4627082.html

https://www.cnblogs.com/yw-technology/p/7476106.html

線程安全

什麼叫線程安全?servlet是線程安全嗎?

線程安全就是說多線程訪問同一代碼,不會產生不肯定的結果。

在多線程環境中,當各線程不共享數據的時候,即都是私有(private)成員,那麼必定是線程安全的。但這種狀況並很少見,在多數狀況下須要共享數據,這時就須要進行適當的同步控制了。

線程安全通常都涉及到synchronized, 就是一段代碼同時只能有一個線程來操做 否則中間過程可能會產生不可預製的結果。

若是你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。若是每次運行結果和單線程運行的結果是同樣的,並且其餘的變量的值也和預期的是同樣的,就是線程安全的。

Servlet不是線程安全的,詳見:漫畫 | Servlet屬於線程安全的嗎?

同步有幾種實現方法? 

1.同步方法

即有synchronized關鍵字修飾的方法。

因爲java的每一個對象都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用該方法前,須要得到內置鎖,不然就處於阻塞狀態。

2.同步代碼塊

即有synchronized關鍵字修飾的語句塊。

被該關鍵字修飾的語句塊會自動被加上內置鎖,從而實現同步。

3.使用特殊域變量(volatile)實現線程同步

a.volatile關鍵字爲域變量的訪問提供了一種免鎖機制,

b.使用volatile修飾域至關於告訴虛擬機該域可能會被其餘線程更新,

c.所以每次使用該域就要從新計算,而不是使用寄存器中的值

d.volatile不會提供任何原子操做,它也不能用來修飾final類型的變量

4.使用重入鎖實現線程同步

在JavaSE5.0中新增了一個java.util.concurrent包來支持同步。ReentrantLock類是可重入、互斥、實現了Lock接口的鎖,它與使用synchronized方法和快具備相同的基本行爲和語義,而且擴展了其能力。

5.使用局部變量實現線程同步。

參考:

https://www.cnblogs.com/jiansen/p/7351872.html

 

volatile有什麼用?可否用一句話說明下volatile的應用場景?

做用是:做爲指令關鍵字,確保本條指令不會因編譯器的優化而省略,且要求每次直接讀值,即不是從寄存器裏取備份值,而是去該地址內存存儲的值。

一句話說明volatile的應用場景:

對變量的寫操做不依賴於當前值且該變量沒有包含在具備其餘變量的不變式中。

請說明下java的內存模型。

Java內存模型的邏輯視圖

爲了保證併發編程中能夠知足原子性、可見性及有序性。有一個重要的概念,那就是內存模型。

爲了保證共享內存的正確性(可見性、有序性、原子性),內存模型定義了共享內存系統中多線程程序讀寫操做行爲的規範。

經過這些規則來規範對內存的讀寫操做,從而保證指令執行的正確性。它與處理器有關、與緩存有關、與併發有關、與編譯器也有關。

它解決了 CPU 多級緩存、處理器優化、指令重排等致使的內存訪問問題,保證了併發場景下的一致性、原子性和有序性。

內存模型解決併發問題主要採用兩種方式:

  1. 限制處理器優化
  2. 使用內存屏障

關於主內存與工做內存之間的具體交互協議,即一個變量如何從主內存拷貝到工做內存、如何從工做內存同步到主內存之間的實現細節,Java內存模型定義瞭如下八種操做來完成:

  • lock(鎖定):做用於主內存的變量,把一個變量標識爲一條線程獨佔狀態。
  • unlock(解鎖):做用於主內存變量,把一個處於鎖定狀態的變量釋放出來,釋放後的變量才能夠被其餘線程鎖定。
  • read(讀取):做用於主內存變量,把一個變量值從主內存傳輸到線程的工做內存中,以便隨後的load動做使用
  • load(載入):做用於工做內存的變量,它把read操做從主內存中獲得的變量值放入工做內存的變量副本中。
  • use(使用):做用於工做內存的變量,把工做內存中的一個變量值傳遞給執行引擎,每當虛擬機遇到一個須要使用變量的值的字節碼指令時將會執行這個操做。
  • assign(賦值):做用於工做內存的變量,它把一個從執行引擎接收到的值賦值給工做內存的變量,每當虛擬機遇到一個給變量賦值的字節碼指令時執行這個操做。
  • store(存儲):做用於工做內存的變量,把工做內存中的一個變量的值傳送到主內存中,以便隨後的write的操做。
  • write(寫入):做用於主內存的變量,它把store操做從工做內存中一個變量的值傳送到主內存的變量中。

若是要把一個變量從主內存中複製到工做內存,就須要按順尋地執行read和load操做,若是把變量從工做內存中同步回主內存中,就要按順序地執行store和write操做。Java內存模型只要求上述操做必須按順序執行,而沒有保證必須是連續執行。也就是read和load之間,store和write之間是能夠插入其餘指令的,如對主內存中的變量a、b進行訪問時,可能的順序是read a,read b,load b, load a。Java內存模型還規定了在執行上述八種基本操做時,必須知足以下規則:

  • 不容許read和load、store和write操做之一單獨出現
  • 不容許一個線程丟棄它的最近assign的操做,即變量在工做內存中改變了以後必須同步到主內存中。
  • 不容許一個線程無緣由地(沒有發生過任何assign操做)把數據從工做內存同步回主內存中。
  • 一個新的變量只能在主內存中誕生,不容許在工做內存中直接使用一個未被初始化(load或assign)的變量。即就是對一個變量實施use和store操做以前,必須先執行過了assign和load操做。
  • 一個變量在同一時刻只容許一條線程對其進行lock操做,lock和unlock必須成對出現
  • 若是對一個變量執行lock操做,將會清空工做內存中此變量的值,在執行引擎使用這個變量前須要從新執行load或assign操做初始化變量的值
  • 若是一個變量事先沒有被lock操做鎖定,則不容許對它執行unlock操做;也不容許去unlock一個被其餘線程鎖定的變量。
  • 對一個變量執行unlock操做以前,必須先把此變量同步到主內存中(執行store和write操做)。

參考:

https://www.cnblogs.com/nexiyi/p/java_memory_model_and_thread.html

http://ifeve.com/java-memory-model-6/

爲何代碼會重排序?

直接參考:

https://www.cnblogs.com/toov5/p/9831696.html

https://blog.csdn.net/qq_32646795/article/details/78221064

併發容器和框架

如何讓一段程序併發的執行,並最終彙總結果?

使用CyclicBarrier 在多個關口處將多個線程執行結果彙總, CountDownLatch 在各線程執行完畢後向總線程彙報結果。

CountDownLatch : 一個線程(或者多個), 等待另外N個線程完成某個事情以後才能執行。

CyclicBarrier : N個線程相互等待,任何一個線程完成以前,全部的線程都必須等待。

這樣應該就清楚一點了,對於CountDownLatch來講,重點是那個「一個線程」, 是它在等待,而另外那N的線程在把「某個事情」作完以後能夠繼續等待,能夠終止。而對於CyclicBarrier來講,重點是那N個線程,他們之間任何一個沒有完成,全部的線程都必須等待。

從api上理解就是CountdownLatch有主要配合使用兩個方法countDown()和await(),countDown()是作事的線程用的方法,await()是等待事情完成的線程用個方法,這兩種線程是能夠分開的(下面例子:CountdownLatchTest2),固然也能夠是同一組線程;CyclicBarrier只有一個方法await(),指的是作事線程必須你們同時等待,必須是同一組線程的工做。

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
/**
 * 各個線程執行完成後,主線程作總結性工做的例子
 * @author xuexiaolei
 * @version 2017年11月02日
 */
public class CountdownLatchTest2 {
    private final static int THREAD_NUM = 10;
    public static void main(String[] args) {
        CountDownLatch lock = new CountDownLatch(THREAD_NUM);
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < THREAD_NUM; i++) {
            exec.submit(new CountdownLatchTask(lock, "Thread-"+i));
        }
        try {
            lock.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("你們都執行完成了,作總結性工做");
        exec.shutdown();
    }
 
    static class CountdownLatchTask implements Runnable{
        private final CountDownLatch lock;
        private final String threadName;
        CountdownLatchTask(CountDownLatch lock, String threadName) {
            this.lock = lock;
            this.threadName = threadName;
        }
        @Override public void run() {
            System.out.println(threadName + " 執行完成");
            lock.countDown();
        }
    }
}

CyclicBarrier例子:

import java.util.concurrent.*;
 
/**
 *
 * @author xuexiaolei
 * @version 2017年11月02日
 */
public class CyclicBarrierTest {
    private final static int THREAD_NUM = 10;
    public static void main(String[] args) {
        CyclicBarrier lock = new CyclicBarrier(THREAD_NUM, new Runnable() {
            @Override public void run() {
                System.out.println("這階段你們都執行完成了,我總結一下,而後開始下一階段");
            }
        });
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < THREAD_NUM; i++) {
            exec.submit(new CountdownLatchTask(lock, "Task-"+i));
        }
        exec.shutdown();
    }
 
    static class CountdownLatchTask implements Runnable{
        private final CyclicBarrier lock;
        private final String threadName;
        CountdownLatchTask(CyclicBarrier lock, String threadName) {
            this.lock = lock;
            this.threadName = threadName;
        }
        @Override public void run() {
            for (int i = 0; i < 3; i++) {
                System.out.println(threadName + " 執行完成");
                try {
                    lock.await();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
 
        }
    }
}

如何合理的配置java線程池?如CPU密集型的任務,基本線程池應該配置多大?IO密集型的任務,基本線程池應該配置多大?用有界隊列好仍是無界隊列好?任務很是多的時候,使用什麼阻塞隊列能獲取最好的吞吐量?

雖然Exectors能夠生成一些很經常使用的線程池,但畢竟在什麼狀況下使用仍是開發者最清楚的。在某些本身很清楚的使用場景下,java線程池仍是推薦本身配置的。下面是java線程池的配置類的參數,咱們逐一分析一下:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler
  1. corePoolSize - 池中所保存的線程數,包括空閒線程。
  2. maximumPoolSize - 池中容許的最大線程數。
  3. keepAliveTime - 當線程數大於核心時,此爲終止前多餘的空閒線程等待新任務的最長時間。
  4. unit - keepAliveTime 參數的時間單位。
  5. workQueue - 執行前用於保持任務的隊列。此隊列僅保持由 execute 方法提交的 Runnable 任務。用BlocingQueue的實現類均可以。
  6. threadFactory - 執行程序建立新線程時使用的工廠。自定義線程工廠能夠作一些額外的操做,好比統計生產的線程數等。
  7. handler - 飽和策略,即超出線程範圍和隊列容量而使執行被阻塞時所使用的處理程序。策略有:Abort終止並拋出異常,Discard悄悄拋棄任務,Discard-Oldest拋棄最老的任務策略,Caller-Runs將任務退回給調用者策略。

至於線程池應當配置多大的問題,通常有以下的經驗設置:

1. 若是是CPU密集型應用,則線程池大小設置爲N+1。

2. 若是是IO密集型應用,則線程池大小設置爲2N+1。

用有界隊列好仍是無界隊列好?這種問題的答案確定是視狀況而定:

1. 有界隊列有助於避免資源耗盡的狀況發生。但他帶來了新的問題:當隊列填滿後,新的任務怎麼辦?因此有界隊列適用於執行比較耗資源的任務,同時要設計好相應的飽和策略。

2. 無界隊列和有界隊列恰好相反,在資源無限的狀況下能夠一直接收新任務。適用於小任務,請求和處理速度相對持平的情況。

3. 其實還有一種同步移交的隊列 SynchronousQueue ,這種隊列不存儲任務信息,直接將任務提交給線程池。能夠理解爲容量只有1的有界隊列,在特殊場景下有特殊做用,一樣得設計好相應的飽和策略。

參考:https://blog.csdn.net/qq_34039315/article/details/78542498

如何使用阻塞隊列實現一個生產者和消費者模型?請寫代碼。

下面這是一個完整的生產者消費者代碼例子,對比傳統的wait、nofity代碼,它更易於理解。

ProducerConsumerPattern.java以下:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
 
public class ProducerConsumerPattern {
 
    public static void main(String args[]){
 
     //Creating shared object
     BlockingQueue sharedQueue = new LinkedBlockingQueue();
 
     //Creating Producer and Consumer Thread
     Thread prodThread = new Thread(new Producer(sharedQueue));
     Thread consThread = new Thread(new Consumer(sharedQueue));
 
     //Starting producer and Consumer thread
     prodThread.start();
     consThread.start();
    }
}

生產者,Producer.java以下:

class Producer implements Runnable {
 
    private final BlockingQueue sharedQueue;
 
    public Producer(BlockingQueue sharedQueue) {
        this.sharedQueue = sharedQueue;
    }
 
    @Override
    public void run() {
        for(int i=0; i<10; i++){
            try {
                System.out.println("Produced: " + i);
                sharedQueue.put(i);
            } catch (InterruptedException ex) {
                Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

消費者,Consumer.java以下所示:

class Consumer implements Runnable{
 
    private final BlockingQueue sharedQueue;
 
    public Consumer (BlockingQueue sharedQueue) {
        this.sharedQueue = sharedQueue;
    }
 
    @Override
    public void run() {
        while(true){
            try {
                System.out.println("Consumed: "+ sharedQueue.take());
            } catch (InterruptedException ex) {
                Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
 
}

參考:

https://www.cnblogs.com/expiator/p/9317929.html

多讀少寫的場景應該使用哪一個併發容器,爲何使用它?好比你作了一個搜索引擎,搜索引擎每次搜索前須要判斷搜索關鍵詞是否在黑名單裏,黑名單天天更新一次。

Java中的鎖

如何實現樂觀鎖(CAS)?如何避免ABA問題?

CAS是項樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程並不會被掛起,而是被告知此次競爭中失敗,並能夠再次嘗試。

CAS 操做包含三個操做數 —— 內存位置(V)、預期原值(A)和新值(B)。若是內存位置的值與預期原值相匹配,那麼處理器會自動將該位置值更新爲新值。不然,處理器不作任何操做。不管哪一種狀況,它都會在 CAS 指令以前返回該位置的值。(在 CAS 的一些特殊狀況下將僅返回 CAS 是否成功,而不提取當前值。)CAS 有效地說明了「我認爲位置 V 應該包含值 A;若是包含該值,則將 B 放到這個位置;不然,不要更改該位置,只告訴我這個位置如今的值便可。」這其實和樂觀鎖的衝突檢查+數據更新的原理是同樣的。

這裏再強調一下,樂觀鎖是一種思想。CAS是這種思想的一種實現方式。

ABA問題:

好比說一個線程one從內存位置V中取出A,這時候另外一個線程two也從內存中取出A,而且two進行了一些操做變成了B,而後two又將V位置的數據變成A,這時候線程one進行CAS操做發現內存中仍然是A,而後one操做成功。儘管線程one的CAS操做成功,可是不表明這個過程就是沒有問題的。

解決方法:經過版本號(version)的方式來解決,每次比較要比較數據的值和版本號兩項內容便可。

讀寫鎖能夠用於什麼應用場景?

在多線程的環境下,對同一份數據進行讀寫,會涉及到線程安全的問題。好比在一個線程讀取數據的時候,另一個線程在寫數據,而致使先後數據的不一致性;一個線程在寫數據的時候,另外一個線程也在寫,一樣也會致使線程先後看到的數據的不一致性。

這時候能夠在讀寫方法中加入互斥鎖,任什麼時候候只能容許一個線程的一個讀或寫操做,而不容許其餘線程的讀或寫操做,這樣是能夠解決這樣以上的問題,可是效率卻大打折扣了。由於在真實的業務場景中,一份數據,讀取數據的操做次數一般高於寫入數據的操做,而線程與線程間的讀讀操做是不涉及到線程安全的問題,沒有必要加入互斥鎖,只要在讀-寫,寫-寫期間上鎖就好了。

對於以上這種狀況,讀寫鎖是最好的解決方案!其中它的實現類:ReentrantReadWriteLock--顧名思義是可重入的讀寫鎖,容許多個讀線程得到ReadLock,但只容許一個寫線程得到WriteLock

讀寫鎖的機制:

  1. "讀-讀" 不互斥
  2. "讀-寫" 互斥
  3. "寫-寫" 互斥

參考:https://www.cnblogs.com/liang1101/p/6475555.html

何時應該使用可重入鎖?

可重入鎖,也叫作遞歸鎖,指的是同一線程 外層函數得到鎖以後 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。

在JAVA環境下 ReentrantLock 和synchronized 都是 可重入鎖。

參考:http://ifeve.com/java_lock_see4/

什麼場景下可使用volatile替換synchronized?

狀態標誌:把簡單地volatile變量做爲狀態標誌,來達成線程之間通信的目的,省去了用synchronized還要wait,notify或者interrupt的編碼麻煩。

替換重量級鎖:若是某個變量僅是單次讀或者單次寫操做,沒有複合操做(i++,先檢查後判斷之類的)就能夠用volatile替換synchronized。

併發工具

如何實現一個流控程序,用於控制請求的調用次數?

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
 
/**
 * 阻塞訪問的線程,直到獲取了訪問令牌
 * @author xuexiaolei
 * @version 2017年11月15日
 */
public class FlowControl2 {
    private final static int MAX_COUNT = 10;
    private final Semaphore semaphore = new Semaphore(MAX_COUNT);
    private final ExecutorService exec = Executors.newCachedThreadPool();
 
    public void access(int i){
        exec.submit(new Runnable() {
            @Override public void run() {
                semaphore.acquireUninterruptibly();
                doSomething(i);
                semaphore.release();
            }
        });
    }
 
    public void doSomething(int i){
        try {
            Thread.sleep(new Random().nextInt(100));
            System.out.println(String.format("%s 經過線程:%s 訪問成功",i,Thread.currentThread().getName()));
        } catch (InterruptedException e) {
        }
    }
 
    public static void main(String[] args) {
        FlowControl2 web = new FlowControl2();
        for (int i = 0; i < 2000; i++) {
            web.access(i);
        }
    }
}
相關文章
相關標籤/搜索