多線程篇
(一)實現創建線程的兩種方式有哪些
(1)實現Runnable接口
new Thread(new Runnable() {
public void run() {
}
}).start();
(2)繼承 Thread類重寫run方法
new Thread( {
@Override
public void run() {
}
}).start();
(3)具體是創建Callable接口的實現類,實現call()方法。並使用FutureTask類來包裝Callable實現類的對象,且以此FutureTask對象作爲Thread對象的target來創建線程
FutureTask本質上實現了Runnable, Future接口 源碼 public interface RunnableFuture extends Runnable, Future 因此 FutureTask可以做爲target對象。
Callable callable = new Callable<Integer>() {
public Integer call() throws Exception {
System.out.println("我是線程");
return 1;
}
}; // 創建MyCallable對象
FutureTask<Integer> ft = new FutureTask<Integer>(callable); //使用FutureTask來包裝MyCallable對象
new Thread(ft).start(); //FutureTask對象作爲Thread對象的target創建新的線程
(二) Runnable接口和Thread類方法的底層實現是什麼?
(1)Runnable接口只有一個抽象方法 run方法
public abstract void run();
(2)Thread類
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
target 就是現實runnable 接口的對象
(3)總結:因此只有實現runnable接口才走Thread類
裏面的run方法 ,繼承Thread類會走重寫的run方法。
(三)thread.join 使用場景 底層實現?
底層實現:join底層就是調用了wait()方法核心源碼
If (millis == 0) {
while (isAlive()) {
wait(0);
}
wait(0)線程一直等待下去,直到讓它的等待,直到阻塞它的線程對象執行完畢,由jvm調該對象的notifyAll()方法,喚醒所有在該對象上阻塞的線程。
使用場景:A線程和B線程C線程,如果需要ABC順序執行則 這裏需要使用到join方法。在B線程中使用A.JOIN ,C線程中調B.JOIN,
通過源碼我們知道A.JOIN()其實用了A.wait(0),使B線程處於等待狀態
只有待A線程執行完畢,由jvm調用A對象的notifyAll()方法,喚醒所有在A對象上阻塞的線程。
(四) wait(),notifyAll(),notify()的使用?
這三個方法必須使用在被synchronized 修飾的代碼塊或者方法中。
(五)synchronized關鍵字特點
保證原子性 :synchronized關鍵字鎖住的代碼,一次只能被一個線程所操作。
保證可見性:在Java內存模型中,synchronized規定,線程在加鎖時,先清空工作內存,在主存中拷貝最新變量的副本到工作內存,執行完代碼後,將更改後的共享變量的值刷新到主內存中,釋放鎖。
保證有序性:synchronized通過“一個變量 在同一時刻只允許一個線程對它進行lock操作”,這條規則決定了持有同一個鎖的兩個同步塊只能串行的併入
(六) java中同步鎖synchronized與Lock的區別
(1)synchronized是java內置關鍵字,,Lock是個接口(唯一實現類ReentrantLock);
(2)synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖(tryLock())
(3)synchronized會自動釋放鎖(a 線程執行完同步代碼會釋放鎖 ;b 線程執行過程中發生異常會釋放鎖),
Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖;
(4)用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了;
(七)synchronized與volatile的區別
1,volatile不會造成線程的阻塞,synchronized會。
2,synchronized會 造成線程狀態的改變,而線程狀態的改變又依賴於操作系統,所以效率會比較低。
3,synchronized可以修飾代碼塊、方法。volatile只能修飾變量。
4,synchronized能保證原子性、volatile不能。
5,volatile本質是在告訴jvm當前變量在寄存器(工作內存)中的值是不確定的,需要從主存中讀取; synchronized則是鎖定當前變量,只有當前線程可以訪問該變量,其他線程被阻塞住。
(八)什麼是可重入鎖?
可重入鎖又名遞歸鎖,是指在同一個線程在外層方法獲取鎖的時候,在進入內層方法會自動獲取鎖
實例:
、synchronized void setA() throws Exception{
Thread.sleep(1000);
setB();
}
synchronized void setB() throws Exception{
Thread.sleep(1000);
}
setA獲得鎖時setB也會獲得鎖,synchronized就是可重入鎖
(九)Hashtable 和ConcurrentHashMap區別
(1) 相同點:ConcurrentHashMap 和 Hashtable 都是線程安全的
(2) 不同點:Hashtable 底層方法都是synchronized修飾的 只有一把鎖效率低下。
ConcurrentHashMap :(1)底層是分段數組+鏈表實現 被稱爲分段鎖,
(2)將map分成 N個segmen對象,對每個segmen對象單獨加luck鎖
Hashtable的synchronized是針對整張Hash表的,即每次鎖住整張表讓線程獨佔,ConcurrentHashMap中則是一次鎖住一個桶,,原來只能一個線程進入,現在卻能同時有多個寫線程執行,併發性能的提升是顯而易見的。
(十)造成死鎖產生的4個必要條件
互斥:一個資源一次只能被一個進程佔用。
佔有且等待:一個進程佔有一個資源 ,等待別的資源。
不可搶佔:一個進程佔有的資源,不能被別的資源搶佔。
循環等待:存在一個進程鏈 ,每個進程都佔有一個資源,並等待下個進程釋放它佔有的資源的至少 一種資源
(十一)什麼是CAS; 樂觀鎖
CAS,全稱爲Compare and Swap,即比較-替換。假設有三個操作數:內存值V、舊的預期值A、要修改的值B,當且僅當預期值A和內存值V相同時,纔會將內存值修改爲B並返回true,否則什麼都不做並返回false。當然CAS一定要volatile變量配合,這樣才能保證每次拿到的變量是主內存中最新的那個值,否則舊的預期值A對某條線程來說,永遠是一個不會變的值A,只要某次CAS操作失敗,永遠都不可能成功。
(十二)什麼是樂觀鎖和悲觀鎖
(1)樂觀鎖:就像它的名字一樣,對於併發間操作產生的線程安全問題持樂觀狀態,樂觀鎖認爲競爭不總是會發生,因此它不需要持有鎖,將比較-替換這兩個動作作爲一個原子操作嘗試去修改內存中的變量,如果失敗則表示發生衝突,那麼就應該有相應的重試邏輯。
(2)悲觀鎖:還是像它的名字一樣,對於併發間操作產生的線程安全問題持悲觀狀態,悲觀鎖認爲競爭總是會發生,因此每次對某資源進行操作時,都會持有一個獨佔的鎖,就像synchronized,不管三七二十一,直接上了鎖就操作資源了。
(十三)ThreadLocal是什麼?怎麼用?
ThreadLocal的作用是提供線程內的局部變量,這種變量在線程的生命週期內起作用。作用:提供一個線程內公共變量(比如本次請求的用戶信息),減少同一個線程內多個函數或者組件之間一些公共變量的傳遞的複雜度,或者爲線程提供一個私有的變量副本,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產生影響。
原理:
首先,在Thread類中有一行:
ThreadLocal.ThreadLocalMap threadLocals = null;
其中ThreadLocalMap類的定義是在ThreadLocal類中,真正的引用卻是在Thread類中。同時,ThreadLocalMap中用於存儲數據的entry定義:
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
從中我們可以發現這個Map的key是ThreadLocal類的實例對象,value爲用戶的值,並不是網上大多數的例子key是線程的名字或者標識。ThreadLocal的set和get方法代碼:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
其中的getMap方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
給當前Thread類對象初始化ThreadlocalMap屬性:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
總結:
(1)Thread類中有一個成員變量屬於ThreadLocalMap類(一個定義在ThreadLocal類中的內部類),它是一個Map,他的key是ThreadLocal實例對象。
(2)當爲ThreadLocal類的對象set值時,首先獲得當前線程的ThreadLocalMap類屬性,然後以ThreadLocal類的對象爲key,設定value。get值時則類似。
(3)ThreadLocal變量的活動範圍爲某線程,是該線程“專有的,獨自霸佔”的,對該變量的所有操作均由該線程完成!也就是說,ThreadLocal 不是用來解決共享對象的多線程訪問的競爭問題的,因爲ThreadLocal.set() 到線程中的對象是該線程自己使用的對象,其他線程是不需要訪問的,也訪問不到的。當線程終止後,這些值會作爲垃圾回收。