高并发常见面试题
1、線程與進程
a,地址空間:進程內的一個執行單元;進程至少有一個線程;它們共享進程的地址空間;而進程有自己獨立的地址空間;
b,資源擁有:進程是資源分配和擁有的單位,同一個進程內的線程共享進程的資
c,線程是處理器調度的基本單位,但進程不是.
d,二者均可并發執行.
2、 守護線程
在Java中有兩類線程:用戶線程 (User Thread)、守護線程 (Daemon Thread)。
守護線程和用戶線程的區別在于:守護線程依賴于創建它的線程,而用戶線程則不依賴。舉個簡單的例子:如果在main線程中創建了一個守護線程,當main方法運行完畢之后,守護線程也會隨著消亡。而用戶線程則不會,用戶線程會一直運行直到其運行完畢。在JVM中,像垃圾收集器線程就是守護線程。
3、java thread狀態
4、請說出與線程同步以及線程調度相關的方法。
5、進程調度算法
實時系統:FIFO(First Input First Output,先進先出算法),SJF(Shortest Job First,最短作業優先算法),SRTF(Shortest Remaining Time First,最短剩余時間優先算法)。
交互式系統:RR(Round Robin,時間片輪轉算法),HPF(Highest Priority First,最高優先級算法),多級隊列,最短進程優先,保證調度,彩票調度,公平分享調度。
6、wait()和sleep()的區別
7、ThreadLocal,以及死鎖分析
hreadLocal為每個線程維護一個本地變量。
采用空間換時間,它用于線程間的數據隔離,為每一個使用該變量的線程提供一個副本,每個線程都可以獨立地改變自己的副本,而不會和其他線程的副本沖突。
ThreadLocal類中維護一個Map,用于存儲每一個線程的變量副本,Map中元素的鍵為線程對象,而值為對應線程的變量副本。
徹底理解ThreadLocal
8、Synchronized 與Lock
ReentrantLock 擁有Synchronized相同的并發性和內存語義,此外還多了 鎖投票,定時鎖等候和中斷鎖等候
線程A和B都要獲取對象O的鎖定,假設A獲取了對象O鎖,B將等待A釋放對O的鎖定,
如果使用 synchronized ,如果A不釋放,B將一直等下去,不能被中斷
如果 使用ReentrantLock,如果A不釋放,可以使B在等待了足夠長的時間以后,中斷等待,而干別的事情
ReentrantLock獲取鎖定與三種方式:
a) lock(), 如果獲取了鎖立即返回,如果別的線程持有鎖,當前線程則一直處于休眠狀態,直到獲取鎖
b) tryLock(), 如果獲取了鎖立即返回true,如果別的線程正持有鎖,立即返回false;
c)tryLock(long timeout,TimeUnit unit), 如果獲取了鎖定立即返回true,如果別的線程正持有鎖,會等待參數給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false;
d) lockInterruptibly:如果獲取了鎖定立即返回,如果沒有獲取鎖定,當前線程處于休眠狀態,直到或者鎖定,或者當前線程被別的線程中斷
總體的結論先擺出來:
synchronized:
在資源競爭不是很激烈的情況下,偶爾會有同步的情形下,synchronized是很合適的。原因在于,編譯程序通常會盡可能的進行優化synchronized,另外可讀性非常好,不管用沒用過5.0多線程包的程序員都能理解。
ReentrantLock:
ReentrantLock提供了多樣化的同步,比如有時間限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在資源競爭不激烈的情形下,性能稍微比synchronized差點點。但是當同步非常激烈的時候,synchronized的性能一下子能下降好幾十倍。而ReentrantLock確還能維持常態。
詳解synchronized與Lock的區別與使用
9、Volatile和Synchronized
Volatile和Synchronized四個不同點:
要使 volatile 變量提供理想的線程安全,必須同時滿足下面兩個條件:
JAVA多線程之volatile 與 synchronized 的比較
10、CAS
CAS是樂觀鎖技術,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中一個線程能更新變量的值,而其它線程都失敗,失敗的線程并不會被掛起,而是被告知這次競爭中失敗,并可以再次嘗試。CAS有3個操作數,內存值V,舊的預期值A,要修改的新值B。當且僅當預期值A和內存值V相同時,將內存值V修改為B,否則什么都不做。
11、Java中Unsafe類詳解
Java中Unsafe類詳解
12、線程池
線程池的作用:
在程序啟動的時候就創建若干線程來響應處理,它們被稱為線程池,里面的線程叫工作線程
第一:降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。
第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
第三:提高線程的可管理性。
常用線程池:ExecutorService 是主要的實現類,其中常用的有
Executors.newSingleT
hreadPool(),newFixedThreadPool(),newcachedTheadPool(),newScheduledThreadPool()。
13、ThreadPoolExecutor
構造方法參數說明
corePoolSize:核心線程數,默認情況下核心線程會一直存活,即使處于閑置狀態也不會受存keepAliveTime限制。除非將allowCoreThreadTimeOut設置為true。
maximumPoolSize:線程池所能容納的最大線程數。超過這個數的線程將被阻塞。當任務隊列為沒有設置大小的LinkedBlockingDeque時,這個值無效。
keepAliveTime:非核心線程的閑置超時時間,超過這個時間就會被回收。
unit:指定keepAliveTime的單位,如TimeUnit.SECONDS。當將allowCoreThreadTimeOut設置為true時對corePoolSize生效。
workQueue:線程池中的任務隊列.
常用的有三種隊列,SynchronousQueue,LinkedBlockingDeque,ArrayBlockingQueue。
threadFactory:線程工廠,提供創建新線程的功能。ThreadFactory是一個接口,只有一個方法
原理
13、Executor拒絕策略
catch,否則程序會直接退出.
14、CachedThreadPool 、 FixedThreadPool、SingleThreadPool
適用場景:任務少 ,并且不需要并發執行
線程沒有任務要執行時,便處于空閑狀態,處于空閑狀態的線程并不會被立即銷毀(會被緩存住),只有當空閑時間超出一段時間(默認為60s)后,線程池才會銷毀該線程(相當于清除過時的緩存)。新任務到達后,線程池首先會讓被緩存住的線程(空閑狀態)去執行任務,如果沒有可用線程(無空閑線程),便會創建新的線程。
適用場景:處理任務速度 > 提交任務速度,耗時少的任務(避免無限新增線程)
15、CopyOnWriteArrayList
CopyOnWriteArrayList : 寫時加鎖,當添加一個元素的時候,將原來的容器進行copy,復制出一個新的容器,然后在新的容器里面寫,寫完之后再將原容器的引用指向新的容器,而讀的時候是讀舊容器的數據,所以可以進行并發的讀,但這是一種弱一致性的策略。
使用場景:CopyOnWriteArrayList適合使用在讀操作遠遠大于寫操作的場景里,比如緩存。
16、AQS
- 1
CANCELLED,值為1,表示當前的線程被取消;
SIGNAL,值為-1,表示當前節點的后繼節點包含的線程需要運行,也就是unpark;
CONDITION,值為-2,表示當前節點在等待condition,也就是在condition隊列中;
PROPAGATE,值為-3,表示當前場景下后續的acquireShared能夠得以執行;
值為0,表示當前節點在sync隊列中,等待著獲取鎖。
protected boolean tryAcquire(int arg) : 獨占式獲取同步狀態,試著獲取,成功返回true,反之為false
protected boolean tryRelease(int arg) :獨占式釋放同步狀態,等待中的其他線程此時將有機會獲取到同步狀態;
protected int tryAcquireShared(int arg) :共享式獲取同步狀態,返回值大于等于0,代表獲取成功;反之獲取失敗;
protected boolean tryReleaseShared(int arg) :共享式釋放同步狀態,成功為true,失敗為false
AQS維護一個共享資源state,通過內置的FIFO來完成獲取資源線程的排隊工作。該隊列由一個一個的Node結點組成,每個Node結點維護一個prev引用和next引用,分別指向自己的前驅和后繼結點。雙端雙向鏈表。
acquire
a.首先tryAcquire獲取同步狀態,成功則直接返回;否則,進入下一環節;
b.線程獲取同步狀態失敗,就構造一個結點,加入同步隊列中,這個過程要保證線程安全;
c.加入隊列中的結點線程進入自旋狀態,若是老二結點(即前驅結點為頭結點),才有機會嘗試去獲取同步狀態;否則,當其前驅結點的狀態為SIGNAL,線程便可安心休息,進入阻塞狀態,直到被中斷或者被前驅結點喚醒。
release
release的同步狀態相對簡單,需要找到頭結點的后繼結點進行喚醒,若后繼結點為空或處于CANCEL狀態,從后向前遍歷找尋一個正常的結點,喚醒其對應線程。
共享式地獲取同步狀態.同步狀態的方法tryAcquireShared返回值為int。
a.當返回值大于0時,表示獲取同步狀態成功,同時還有剩余同步狀態可供其他線程獲取;
b.當返回值等于0時,表示獲取同步狀態成功,但沒有可用同步狀態了;
c.當返回值小于0時,表示獲取同步狀態失敗。
非公平鎖中,那些嘗試獲取鎖且尚未進入等待隊列的線程會和等待隊列head結點的線程發生競爭。公平鎖中,在獲取鎖時,增加了isFirst(current)判斷,當且僅當,等待隊列為空或當前線程是等待隊列的頭結點時,才可嘗試獲取鎖。
Java并發包基石-AQS詳解
16、Java里的阻塞隊列
7個阻塞隊列。分別是
ArrayBlockingQueue :一個由數組結構組成的有界阻塞隊列。
LinkedBlockingQueue :一個由鏈表結構組成的有界阻塞隊列。
PriorityBlockingQueue :一個支持優先級排序的無界阻塞隊列。
DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。
SynchronousQueue:一個不存儲元素的阻塞隊列。
LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。
LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。
添加元素
Java中的阻塞隊列接口BlockingQueue繼承自Queue接口。BlockingQueue接口提供了3個添加元素方法。
add:添加元素到隊列里,添加成功返回true,由于容量滿了添加失敗會拋出IllegalStateException異常
offer:添加元素到隊列里,添加成功返回true,添加失敗返回false
put:添加元素到隊列里,如果容量滿了會阻塞直到容量不滿
刪除方法
3個刪除方法
poll:刪除隊列頭部元素,如果隊列為空,返回null。否則返回元素。
remove:基于對象找到對應的元素,并刪除。刪除成功返回true,否則返回false
take:刪除隊列頭部元素,如果隊列為空,一直阻塞到隊列有元素并刪除
17、condition
對Condition的源碼理解,主要就是理解等待隊列,等待隊列可以類比同步隊列,而且等待隊列比同步隊列要簡單,因為等待隊列是單向隊列,同步隊列是雙向隊列。
java condition使用及分
18、DelayQueue
隊列中每個元素都有個過期時間,并且隊列是個優先級隊列,當從隊列獲取元素時候,只有過期元素才會出隊列。
并發隊列-無界阻塞延遲隊列delayqueue原理探究
19、Fork/Join框架
Fork/Join框架是Java 7提供的一個用于并行執行任務的框架,是一個把大任務分割成若干個小任務,最終匯總每個小任務結果后得到大任務結果的框架。Fork/Join框架要完成兩件事情:
1.任務分割:首先Fork/Join框架需要把大的任務分割成足夠小的子任務,如果子任務比較大的話還要對子任務進行繼續分割
2.執行任務并合并結果:分割的子任務分別放到雙端隊列里,然后幾個啟動線程分別從雙端隊列里獲取任務執行。子任務執行完的結果都放在另外一個隊列里,啟動一個線程從隊列里取數據,然后合并這些數據。
在Java的Fork/Join框架中,使用兩個類完成上述操作
1.ForkJoinTask:我們要使用Fork/Join框架,首先需要創建一個ForkJoin任務。該類提供了在任務中執行fork和join的機制。通常情況下我們不需要直接集成ForkJoinTask類,只需要繼承它的子類,Fork/Join框架提供了兩個子類:
a.RecursiveAction:用于沒有返回結果的任務
b.RecursiveTask:用于有返回結果的任務
2.ForkJoinPool:ForkJoinTask需要通過ForkJoinPool來執行
任務分割出的子任務會添加到當前工作線程所維護的雙端隊列中,進入隊列的頭部。當一個工作線程的隊列里暫時沒有任務時,它會隨機從其他工作線程的隊列的尾部獲取一個任務(工作竊取算法)。
Fork/Join框架的實現原理
ForkJoinPool由ForkJoinTask數組和ForkJoinWorkerThread數組組成,ForkJoinTask數組負責將存放程序提交給ForkJoinPool,而ForkJoinWorkerThread負責執行這
20、原子操作類
在java.util.concurrent.atomic包下,可以分為四種類型的原子更新類:原子更新基本類型、原子更新數組類型、原子更新引用和原子更新屬性。
使用原子方式更新基本類型,共包括3個類:
AtomicBoolean:原子更新布爾變量
AtomicInteger:原子更新整型變量
AtomicLong:原子更新長整型變量
通過原子更新數組里的某個元素,共有3個類:
AtomicIntegerArray:原子更新整型數組的某個元素
AtomicLongArray:原子更新長整型數組的某個元素
AtomicReferenceArray:原子更新引用類型數組的某個元素
AtomicIntegerArray常用的方法有:
int addAndSet(int i, int delta):以原子方式將輸入值與數組中索引為i的元素相加
boolean compareAndSet(int i, int expect, int update):如果當前值等于預期值,則以原子方式更新數組中索引為i的值為update值
AtomicReference:原子更新引用類型
AtomicReferenceFieldUpdater:原子更新引用類型里的字段
AtomicMarkableReference:原子更新帶有標記位的引用類型。
如果需要原子更新某個類的某個字段,就需要用到原子更新字段類,可以使用以下幾個類:
AtomicIntegerFieldUpdater:原子更新整型字段
AtomicLongFieldUpdater:原子更新長整型字段
AtomicStampedReference:原子更新帶有版本號的引用類型。
要想原子更新字段,需要兩個步驟:
每次必須使用newUpdater創建一個更新器,并且需要設置想要更新的類的字段
更新類的字段(屬性)必須為public volatile
21、同步屏障CyclicBarrier
CyclicBarrier 的字面意思是可循環使用(Cyclic)的屏障(Barrier)。它要做的事情是,讓一組線程到達一個屏障(也可以叫同步點)時被阻塞,直到最后一個線程到達屏障時,屏障才會開門,所有被屏障攔截的線程才會繼續干活。CyclicBarrier默認的構造方法是CyclicBarrier(int parties),其參數表示屏障攔截的線程數量,每個線程調用await方法告訴CyclicBarrier我已經到達了屏障,然后當前線程被阻塞。
CyclicBarrier和CountDownLatch的區別
CountDownLatch的計數器只能使用一次。而CyclicBarrier的計數器可以使用reset() 方法重置。所以CyclicBarrier能處理更為復雜的業務場景,比如如果計算發生錯誤,可以重置計數器,并讓線程們重新執行一次。
CyclicBarrier還提供其他有用的方法,比如getNumberWaiting方法可以獲得CyclicBarrier阻塞的線程數量。isBroken方法用來知道阻塞的線程是否被中斷。比如以下代碼執行完之后會返回true。
22、Semaphore
Semaphore(信號量)是用來控制同時訪問特定資源的線程數量,它通過協調各個線程,以保證合理的使用公共資源
Semaphore可以用于做流量控制,特別公用資源有限的應用場景,比如數據庫連接。假如有一個需求,要讀取幾萬個文件的數據,因為都是IO密集型任務,我們可以啟動幾十個線程并發的讀取,但是如果讀到內存后,還需要存儲到數據庫中,而數據庫的連接數只有10個,這時我們必須控制只有十個線程同時獲取數據庫連接保存數據,否則會報錯無法獲取數據庫連接。這個時候,我們就可以使用Semaphore來做流控,代碼如下:
控制并發線程數的Semaphore
23、死鎖,以及解決死鎖
死鎖產生的四個必要條件
互斥條件:資源是獨占的且排他使用,進程互斥使用資源,即任意時刻一個資源只能給一個進程使用,其他進程若申請一個資源,而該資源被另一進程占有時,則申請者等待直到資源被占有者釋放。
不可剝奪條件:進程所獲得的資源在未使用完畢之前,不被其他進程強行剝奪,而只能由獲得該資源的進程資源釋放。
請求和保持條件:進程每次申請它所需要的一部分資源,在申請新的資源的同時,繼續占用已分配到的資源。
循環等待條件:在發生死鎖時必然存在一個進程等待隊列{P1,P2,…,Pn},其中P1等待P2占有的資源,P2等待P3占有的資源,…,Pn等待P1占有的資源,形成一個進程等待環路,環路中每一個進程所占有的資源同時被另一個申請,也就是前一個進程占有后一個進程所深情地資源。
解決死鎖
一是死鎖預防,就是不讓上面的四個條件同時成立。
二是,合理分配資源。
三是使用銀行家算法,如果該進程請求的資源操作系統剩余量可以滿足,那么就分配。
24、進程間的通信方式
中斷
interrupt()的作用是中斷本線程。
本線程中斷自己是被允許的;其它線程調用本線程的interrupt()方法時,會通過checkAccess()檢查權限。這有可能拋出SecurityException異常。
如果本線程是處于阻塞狀態:調用線程的wait(), wait(long)或wait(long, int)會讓它進入等待(阻塞)狀態,或者調用線程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也會讓它進入阻塞狀態。若線程在阻塞狀態時,調用了它的interrupt()方法,那么它的“中斷狀態”會被清除并且會收到一個InterruptedException異常。例如,線程通過wait()進入阻塞狀態,此時通過interrupt()中斷該線程;調用interrupt()會立即將線程的中斷標記設為“true”,但是由于線程處于阻塞狀態,所以該“中斷標記”會立即被清除為“false”,同時,會產生一個InterruptedException的異常。
如果線程被阻塞在一個Selector選擇器中,那么通過interrupt()中斷它時;線程的中斷標記會被設置為true,并且它會立即從選擇操作中返回。
如果不屬于前面所說的情況,那么通過interrupt()中斷線程時,它的中斷標記會被設置為“true”。
中斷一個“已終止的線程”不會產生任何操作。
通常,我們通過“中斷”方式終止處于“阻塞狀態”的線程。
當線程由于被調用了sleep(), wait(), join()等方法而進入阻塞狀態;若此時調用線程的interrupt()將線程的中斷標記設為true。由于處于阻塞狀態,中斷標記會被清除,同時產生一個InterruptedException異常。將InterruptedException放在適當的為止就能終止線程,
interrupted() 和 isInterrupted()的區別
最后談談 interrupted() 和 isInterrupted()。
interrupted() 和 isInterrupted()都能夠用于檢測對象的“中斷標記”。
區別是,interrupted()除了返回中斷標記之外,它還會清除中斷標記(即將中斷標記設為false);而isInterrupted()僅僅返回中斷標記。
interrupt()和線程終止方式
轉載于:https://www.cnblogs.com/wangjintao-0623/p/9727272.html
總結
- 上一篇: 从计算机体系结构方面思考深度学习
- 下一篇: 前端介绍开始(—)