敲開阿里大門的執行緒、多執行緒和執行緒池面試專題
本篇文章主要介紹
Android
開發中的部分知識點,透過閱讀本篇文章,您將收穫以下內容:
1、開啟執行緒的三種方式?
1)繼承
Thread
類,重寫
run()
方法,在
run()
方法體中編寫要完成的任務
new Thread().start();
2)實現
Runnable
介面,實現
run()
方法
new Thread(new MyRunnable()).start();
3)實現
Callable
介面
MyCallable
類,實現
call()
方法,使用
FutureTask
類來包裝
Callable
物件,使用
FutureTask
物件作為
Thread
物件的
target
建立並啟動執行緒;呼叫
FutureTask
物件的
get()
方法來獲得子執行緒執行結束後的返回值。
FutureTask<Integer> ft = new FutureTask<Integer>(new MyCallable());new Thread(ft).start();
2、run()和start()方法區別
run()
方法只是執行緒的主體方法,和普通方法一樣,不會建立新的執行緒。
只有呼叫
start()
方法,才會啟動一個新的執行緒,新執行緒才會呼叫
run()
方法,執行緒才會開始執行。
3、如何控制某個方法允許併發訪問執行緒的個數?
建立
Semaphore
變數,
Semaphore semaphore = new Semaphore(5, true);
當方法進入時,請求一個訊號,如果訊號被用完則等待,方法執行完,釋放一個訊號,釋放的訊號新的執行緒就可以使用。
4、在Java中wait和seelp方法的不同
wait()
方法屬於
Object
類,呼叫該方法時,執行緒會放棄物件鎖,只有該物件呼叫
notify()
方法後本執行緒才進入物件鎖定池準備獲取物件鎖進入執行狀態。
sleep()
方法屬於
Thread
類,
sleep()
導致程式暫停執行指定的時間,讓出
CPU
,但它的監控狀態依然儲存著,當指定時間到了又會回到執行狀態,
sleep()
方法中執行緒不會釋放物件鎖。
5、談談wait/notify關鍵字的理解
notify:
喚醒在此物件監視器上等待的單個執行緒
notifyAll():
通知所有等待該競爭資源的執行緒
wait:
釋放
obj
的鎖,導致當前的執行緒等待,直接其他執行緒呼叫此物件的
notify()
或
notifyAll()
方法
當要呼叫
wait()
或
notify()/notifyAll()
方法時,一定要對競爭資源進行加鎖,一般放到
synchronized(obj)
程式碼中。當呼叫
obj.notify/notifyAll
後,呼叫執行緒依舊持有
obj
鎖,因此等待執行緒雖被喚醒,但仍無法獲得
obj
鎖,直到呼叫執行緒退出
synchronized
塊,釋放
obj
鎖後,其他等待執行緒才有機會獲得鎖繼續執行。
6、什麼導致執行緒阻塞?
(1)一般執行緒阻塞
1)執行緒執行了
Thread.sleep(int millsecond)
方法,放棄
CPU
,睡眠一段時間,一段時間過後恢復執行;
2)執行緒執行一段同步程式碼,但無法獲得相關的同步鎖,只能進入阻塞狀態,等到獲取到同步鎖,才能恢復執行;
3)執行緒執行了一個物件的
wait()
方法,直接進入阻塞態,等待其他執行緒執行
notify()/notifyAll()
操作;
4)執行緒執行某些
IO
操作,因為等待相關資源而進入了阻塞態,如
System.in
,但沒有收到鍵盤的輸入,則進入阻塞態。
5)執行緒禮讓,
Thread.yield()
方法,暫停當前正在執行的執行緒物件,把執行機會讓給相同或更高優先順序的執行緒,但並不會使執行緒進入阻塞態,執行緒仍處於可執行態,隨時可能再次分得
CPU
時間。執行緒自閉,
join()
方法,在當前執行緒呼叫另一個執行緒的
join()
方法,則當前執行緒進入阻塞態,直到另一個執行緒執行結束,當前執行緒再由阻塞轉為就緒態。
6)執行緒執行
suspend()
使執行緒進入阻塞態,必須
resume()
方法被呼叫,才能使執行緒重新進入可執行狀態。
7、執行緒如何關閉?
- 使用標誌位
2)使用
stop()
方法,但該方法就像關掉電腦電源一樣,可能會發生預料不到的問題
3)使用中斷
interrupt()
public class Thread { // 中斷當前執行緒 public void interrupt(); // 判斷當前執行緒是否被中斷 public boolen isInterrupt(); // 清除當前執行緒的中斷狀態,並返回之前的值 public static boolen interrupted(); }
但呼叫
interrupt()
方法只是傳遞中斷請求訊息,並不代表要立馬停止目標執行緒。
8、講一下java中的同步的方法
之所以需要同步,因為在多執行緒併發控制,當多個執行緒同時操作一個可共享的資源時,如果沒有采取同步機制,將會導致資料不準確,因此需要加入同步鎖,確保在該執行緒沒有完成操作前被其他執行緒呼叫,從而保證該變數的唯一一性和準確性。
1)synchronized修飾同步程式碼塊或方法
由於
java
的每個物件都有一個內建鎖,用此關鍵字修飾方法時,內建鎖會保護整個方法。在呼叫該方法前,需獲得內建鎖,否則就處於陰塞狀態。
2)volatile修飾變數
保證變數線上程間的可見性,每次執行緒要訪問
volatile
修飾的變數時都從記憶體中讀取,而不快取中,這樣每個執行緒訪問到的變數都是一樣的。且使用記憶體屏障。
3)ReentrantLock重入鎖,它常用的方法有ReentrantLock():
建立一個
ReentrantLock
例項
lock()
獲得鎖
unlock()
釋放鎖
4)使用區域性變數ThreadLocal實現執行緒同步
每個執行緒都會儲存一份該變數的副本,副本之間相互獨立,這樣每個執行緒都可以隨意修改自己的副本,而不影響其他執行緒。常用方法
ThreadLocal()
建立一個執行緒本地變數;
get()
返回此執行緒區域性的當前執行緒副本變數;
initialValue()
返回此執行緒區域性變數的當前執行緒的初始值;
set(T value)
將此執行緒變數的當前執行緒副本中的值設定為
value
5) 使用原子變數
如
AtomicInteger
,常用方法
AtomicInteger(int value)
建立個有給定初始值的
AtomicInteger
整數;
addAndGet(int data)
以原子方式將給定值與當前值相加
6)使用阻塞佇列實現執行緒同步
例如
LinkedBlockingQueue<E>
9、如何保證執行緒安全?
執行緒安全性體現在三方法:
1)原子性:
提供互斥訪問,同一時刻只能有一個線和至資料進行操作。
JDK
中提供了很多
atomic
類,如
AtomicInteger\AtomicBoolean\AtomicLong
,它們是透過
CAS
完成原子性。
JDK
提供鎖分為兩種:
synchronized
依賴
JVM
實現鎖,該關鍵字作用物件的作用範圍內同一時刻只能有一個執行緒進行操作。另一種
LOCK
,是
JDK
提供的程式碼層面的鎖,依賴
CPU
指令,代表性是
ReentrantLock
。
2)可見性:
一個執行緒對主記憶體的修改及時被其他執行緒看到。
JVM
提供了
synchronized
和
volatile
,
volatile
的可見性是透過記憶體屏障和禁止重排序實現的,
volatile
會在寫操作時,在寫操作後加一條
store
屏障指令,將本地記憶體中的共享變數值重新整理到主記憶體;會在讀操作時,在讀操作前加一條
load
指令,從記憶體中讀取共享變數。
3)有序性:
指令沒有被編譯器重排序。
可透過
volatile、synchronized、Lock
保證有序性。
10、兩個程式同時要求寫或者讀,能不能實現?如何防止程式的同步?
我認為可以實現,比如兩個程式都讀取日曆程式資料是沒有問題,但同時寫,應該會有衝突。
可以使用共享記憶體實現程式間資料共享。
11、執行緒間操作List
多執行緒數量的問題,一般情況下,多執行緒數量要等於機器
CPU
核數
-1
.
1.如何讓n個執行緒順序遍歷含有n個元素的List集合
import java.util.ArrayList;import java.util.List;import org.apache.commons.lang3.ArrayUtils;public class Test_4 { /** * 多執行緒處理list * * @param data 資料list * @param threadNum 執行緒數 */ public synchronized void handleList(List<String> data, int threadNum) { int length = data.size(); int tl = length % threadNum == 0 ? length / threadNum : (length / threadNum + 1); for (int i = 0; i < threadNum; i++) { int end = (i + 1) * tl; HandleThread thread = new HandleThread("執行緒[" + (i + 1) + "] ", data, i * tl, end > length ? length : end); thread.start(); } } class HandleThread extends Thread { private String threadName; private List<String> data; private int start; private int end; public HandleThread(String threadName, List<String> data, int start, int end) { this.threadName = threadName; this.data = data; this.start = start; this.end = end; } public void run() { List<String> subList = data.subList(start, end)/*.add("^&*")*/; System.out.println(threadName+"處理了"+subList.size()+"條!"); } } public static void main(String[] args) { Test_4 test = new Test_4(); // 準備資料 List<String> data = new ArrayList<String>(); for (int i = 0; i < 6666; i++) { data.add("item" + i); } test.handleList(data, 5); System.out.println(ArrayUtils.toString(data)); } }
2. List多執行緒併發讀取讀取現有的list物件
//測試讀取List的執行緒類,大概34秒package com.thread.list;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class Main { public static void main(String[] args) { List<String> list = new ArrayList<String>(); Map<Long,Integer> map = new HashMap<Long,Integer>(); for(int i = 0;i<1000;i++){ list.add(""+i); } int pcount = Runtime.getRuntime().availableProcessors(); long start = System.currentTimeMillis(); for(int i=0;i<pcount;i++){ Thread t = new MyThread1(list,map); map.put(t.getId(),Integer.valueOf(i)); t.start(); try { t.join(); } catch (InterruptedException e) { e.printStackTrace(); } // System.out.println(list.get(i)); } System.out.println("----"+(System.currentTimeMillis() - start)); } }//執行緒類package com.thread.list;import java.util.List;import java.util.Map;public class MyThread1 extends Thread { private List<String> list; private Map<Long,Integer> map; public MyThread1(List<String> list,Map<Long,Integer> map){ this.list = list; this.map = map; } @Override public void run() { int pcount = Runtime.getRuntime().availableProcessors(); int i = map.get(Thread.currentThread().getId()); for(;i<list.size();i+=pcount){ System.out.println(list.get(i)); } } }
3.多執行緒分段處理List集合
場景:大資料
List
集合,需要對
List
集合中的資料同標準庫中資料進行對比,生成新增,更新,取消資料
解決方案:
List
集合分段,
動態建立執行緒池
newFixedThreadPool
將對比操作在多執行緒中實現
public static void main(String[] args) throws Exception { // 開始時間 long start = System.currentTimeMillis(); List<String> list = new ArrayList<String>(); for (int i = 1; i <= 3000; i++) { list.add(i + ""); } // 每500條資料開啟一條執行緒 int threadSize = 500; // 總資料條數 int dataSize = list.size(); // 執行緒數 int threadNum = dataSize / threadSize + 1; // 定義標記,過濾threadNum為整數 boolean special = dataSize % threadSize == 0; // 建立一個執行緒池 ExecutorService exec = Executors.newFixedThreadPool(threadNum); // 定義一個任務集合 List<Callable<Integer>> tasks = new ArrayList<Callable<Integer>>(); Callable<Integer> task = null; List<String> cutList = null; // 確定每條執行緒的資料 for (int i = 0; i < threadNum; i++) { if (i == threadNum - 1) { if (special) { break; } cutList = list.subList(threadSize * i, dataSize); } else { cutList = list.subList(threadSize * i, threadSize * (i + 1)); } // System.out.println("第" + (i + 1) + "組:" + cutList.toString()); final List<String> listStr = cutList; task = new Callable<Integer>() { @Override public Integer call() throws Exception { System.out.println(Thread.currentThread().getName() + "執行緒:" + listStr); return 1; } }; // 這裡提交的任務容器列表和返回的Future列表存在順序對應的關係 tasks.add(task); } List<Future<Integer>> results = exec.invokeAll(tasks); for (Future<Integer> future : results) { System.out.println(future.get()); } // 關閉執行緒池 exec.shutdown(); System.out.println("執行緒任務執行結束"); System.err.println("執行任務消耗了 :" + (System.currentTimeMillis() - start) + "毫秒"); }
12、Java中物件的生命週期
1)建立階段(Created):
為物件分配儲存空間,開始構造物件,從超類到子類對
static
成員初始化;超類成員變數按順序初始化,遞迴呼叫超類的構造方法,子類成員變數按順序初始化,子類構造方法呼叫。
2)應用階段(In Use):
物件至少被一個強引用持有著。
3)不可見階段(Invisible):
程式執行已超出物件作用域
4)不可達階段(Unreachable):
該物件不再被強引用所持有
5)收集階段(Collected):
假設該物件重寫了
finalize()
方法且未執行過,會去執行該方法。
6)終結階段(Finalized):
物件執行完
finalize()
方法仍處於不可達狀態,等待垃圾回收器對該物件空間進行回收。
7)物件空間重新分配階段(De-allocated):
垃圾回收器對該物件所佔用的記憶體空間進行回收或再分配,該物件徹底消失。
13、static synchronized 方法的多執行緒訪問和作用
static synchronized
控制的是類的所有例項訪問,不管
new
了多少物件,只有一份,所以對該類的所有物件都加了鎖。限制多執行緒中該類的所有例項同時訪問
JVM
中該類對應的程式碼。
14、同一個類裡面兩個synchronized方法,兩個執行緒同時訪問的問題
如果
synchronized
修飾的是靜態方法,鎖的是當前類的
class
物件,進入同步程式碼前要獲得當前類物件的鎖;
普通方法,鎖的是當前例項物件,進入同步程式碼前要獲得的是當前例項的鎖;
同步程式碼塊,鎖的是括號裡面的物件,對給定的物件加鎖,進入同步程式碼塊庫前要獲得給定物件鎖;
如果兩個執行緒訪問同一個物件的
synchronized
方法,會出現競爭,如果是不同物件,則不會相互影響。
15、volatile的原理
有
volatile
變數修飾的共享變數進行寫操作的時候會多一條彙編程式碼,
lock addl $0x0,lock
字首的指令在多核處理器下會將當前處理器快取行的資料會寫回到系統記憶體,這個寫回記憶體的操作會引起在其他
CPU
裡快取了該記憶體地址的資料無效。同時
lock
字首也相當於一個記憶體屏障,對記憶體操作順序進行了限制。
16、synchronized原理
synchronized
透過物件的物件頭
(markword)
來實現鎖機制,
java
每個物件都有物件頭,都可以為
synchronized
實現提供基礎,都可以作為鎖物件,在位元組碼層面
synchronized
塊是透過插入
monitorenter monitorexit
完成同步的。持有
monitor
物件,透過進入、退出這個
Monitor
物件來實現鎖機制。
17、談談NIO的理解
NIO( New Input/ Output)
引入了一種基於通道和緩衝區的
I/O
方式,它可以使用
Native
函式庫直接分配堆外記憶體,然後透過一個儲存在
Java
堆的
DirectByteBuffer
物件作為這塊記憶體的引用進行操作,避免了在
Java
堆和
Native
堆中來回複製資料。
NIO
是一種同步非阻塞的
IO
模型。同步是指執行緒不斷輪詢
IO
事件是否就緒,非阻塞是指執行緒在等待
IO
的時候,可以同時做其他任務。同步的核心就是
Selector,Selector
代替了執行緒本身輪詢
IO
事件,避免了阻塞同時減少了不必要的執行緒消耗;非阻塞的核心就是通道和緩衝區,當
IO
事件就緒時,可以透過寫道緩衝區,保證
IO
的成功,而無需執行緒阻塞式地等待。
18.ReentrantLock 、Lock、synchronized和volatile比較
1)volatile:
解決變數在多個執行緒間的可見性,但不能保證原子性,只能用於修飾變數,不會發生阻塞。
volatile
能遮蔽編譯指令重排,不會把其後面的指令排到記憶體屏障之前的位置,也不會把前面的指令排到記憶體屏障的後面。多用於平行計算的單例模式。
volatile
規定
CPU
每次都必須從記憶體讀取資料,不能從
CPU
快取中讀取,保證了多執行緒在多
CPU
計算中永遠拿到的都是最新的值。
2)synchronized:
互斥鎖,操作互斥,併發執行緒過來,序列獲得鎖,序列執行程式碼。解決的是多個執行緒間訪問共享資源的同步性,可保證原子性,也可間接保證可見性,因為它會將私有記憶體和公有記憶體中的資料做同步。可用來修飾方法、程式碼塊。會出現阻塞。
synchronized
發生異常時,會自動釋放執行緒佔有的鎖,因此不會導致死鎖現象發生。非公平鎖,每次都是相互爭搶資源。
3)lock
是一個介面,而
synchronized
是
java
中的關鍵字,
synchronized
是內建語言的實現。
lock
可以讓等待鎖的執行緒響應中斷。在發生異常時,如果沒有主動透過
unLock()
去釋放鎖,則可能造成死鎖現象,因此使用
Lock
時需要在
finally
塊中釋放鎖。
4)ReentrantLock
可重入鎖,鎖的分配機制是基於執行緒的分配,而不是基於方法呼叫的分配。
ReentrantLock
有
tryLock
方法,如果鎖被其他執行緒持有,返回
false
,可避免形成死鎖。對程式碼加鎖的顆粒會更小,更節省資源,提高程式碼效能。
ReentrantLock
可實現公平鎖和非公平鎖,公平鎖就是先來的先獲取資源。
ReentrantReadWriteLock
用於讀多寫少的場合,且讀不需要互斥場景。
Android學習PDF_原始碼筆記_面試文件_進階影片.pdf
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2664599/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 多執行緒【執行緒池】執行緒
- 執行緒和執行緒池執行緒
- 多執行緒之手撕執行緒池執行緒
- kuangshenshuo-多執行緒-執行緒池執行緒
- java多執行緒9:執行緒池Java執行緒
- Android多執行緒之執行緒池Android執行緒
- Java多執行緒-執行緒池的使用Java執行緒
- 多執行緒面試題執行緒面試題
- 【Java】【多執行緒】執行緒池簡述Java執行緒
- 多執行緒:執行緒池理解和使用總結執行緒
- 執行緒 執行緒池 Task執行緒
- Java面試題:執行緒池內“鬧情緒”的執行緒,怎麼辦?Java面試題執行緒
- C#多執行緒開發-執行緒池03C#執行緒
- 多執行緒和多執行緒同步執行緒
- 執行緒、多執行緒和執行緒池,看完這些你就能全部搞懂了執行緒
- 多執行緒系列(三):執行緒池基礎執行緒
- 多執行緒------執行緒與程式/執行緒排程/建立執行緒執行緒
- Java多執行緒面試高配問題---多執行緒(3)🧵Java執行緒面試
- JavaSE_多執行緒入門 執行緒安全 死鎖 狀態 通訊 執行緒池Java執行緒
- 執行緒與多執行緒執行緒
- 多執行緒--執行緒管理執行緒
- 多執行緒面試題1執行緒面試題
- Linux多執行緒面試題Linux執行緒面試題
- Java執行緒池二:執行緒池原理Java執行緒
- Python執行緒專題10:queue、多執行緒按順序執行Python執行緒
- 深入淺出Java多執行緒(十二):執行緒池Java執行緒
- 多執行緒之間通訊及執行緒池執行緒
- java多執行緒:執行緒池原理、阻塞佇列Java執行緒佇列
- Java併發 之 執行緒池系列 (1) 讓多執行緒不再坑爹的執行緒池Java執行緒
- 二. 執行緒管理之執行緒池執行緒
- 關於執行緒池的面試題執行緒面試題
- 執行緒池建立執行緒的過程執行緒
- Java多執行緒(一)多執行緒入門篇Java執行緒
- Java面試經典題:執行緒池專題Java面試執行緒
- SpringBoot執行緒池和Java執行緒池的實現原理Spring Boot執行緒Java
- Java中命名執行器服務執行緒和執行緒池Java執行緒
- Java 併發:執行緒、執行緒池和執行器全面教程Java執行緒
- Java多執行緒-執行緒中止Java執行緒