1.線程和進程的關係?
一個進程可以有多個線程,但必須有一個線程,一個線程只能在一個進程的範圍內活動。
2.併發和並行的區別?
併發是指有公共資源的爭奪,並行是沒有公共資源的爭奪。
3.實現多線程的方式?
1>.繼承Thred類,重寫run接口。
2>.實現Runnable接口,實現run方法。
3>.通過Callable接口,實現call方法。
4.什麼是守護線程?
專門用於服務其他的線程,如果其他的線程(即用戶自定義線程)都執行完畢,連main線程也執行完畢,那麼jvm就會退出(即停止運行)——此時,連jvm都停止運行了,守護線程當然也就停止執行了
5. synchoried原理?
這個原本是個重量級鎖,但是在後來更新中進行了優化,引入了偏向鎖和輕量級鎖。通過鎖膨脹一步步升級爲重量級鎖。
以前的文章:https://blog.csdn.net/huaixiaohai_1/article/details/92607179
還是說原理吧:JVM基於進入和退出Monitor對象來實現同步和代碼塊同步,同步代碼塊是通過monitorenter和monitorexit實現的。同步方法是通過另一種方式實現的(JVM規範中並沒有說明)。但是還是可以使用這種方法,monitorenter指令是在編譯後插入到同步代碼塊的開始位置,而monitorexit插入在結束或者異常的位置。必須對應。
6.volicate原理?
volicate主要是通過Lock前綴指令來實現的。
lock前綴指令會引起處理器緩存寫回到內存中。當一個處理器的緩存寫回到內存中會導致其他處理器的緩存無效。
還有一個作用是他會禁止指令重排序。
7.禁止指令重排序是什麼原理?
通過內存屏障
8.原子類實現原理?
以AutomicInteger爲例進行源碼分析:
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;
static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}
private volatile int value;
上面這段代碼的意思是:JVM可以實現Java對象的佈局,也就是在內存裏Java對象的各個部分放在哪裏,包括對象的實例字段和一些元數據之類。jdk內部使用的工具類sun.misc.Unsafe提供的方法objectFieldOffset()用於獲取某個字段相對Java對象的“起始地址”的偏移量,可以使用這個偏移量調用getInt、getLong、getObject等方法來獲取某個Java對象的某個字段。
還可以看到value通過volatile保證可見性:
//兩個構造方法
public AtomicInteger(int initialValue) {
value = initialValue;
}
public AtomicInteger() {
}
//get和set方法
public final int get() {
return value;
}
public final void set(int newValue) {
value = newValue;
}
看一下關鍵方法:更新操作
public final int getAndUpdate(IntUnaryOperator updateFunction) {
int prev, next;
do {
prev = get();
next = updateFunction.applyAsInt(prev);
} while (!compareAndSet(prev, next));
return prev;
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
他首先備份了一份原來的值,然後通過cas的方式一直while直到成功。
9. 信號量機制?
信號量機制是爲了保護共享變量,使得一個線程只有一個時刻只有一個進程訪問資源。
主要通過P(申請資源)V(釋放資源)操作來實現信號量,對信號量的操作都是原子性的。
信號量S:用來記錄資源數量,看是否能滿足申請需要。
P(S) 若 S > 0:則S-1;線程繼續執行,若S<0則掛起線程。
V(S) 若S>0;則S+1;若S<0,則喚醒一個線程。
10.AQS是什麼,講講?
抽象的隊列同步器,是除了java自帶的synchronized關鍵字之外的鎖機制。
如果請求的資源空閒,那麼將當前請求資源設置爲有效的工作線程,並將共享資源設置爲鎖定狀態。如果請求的資源鎖定,那麼就進入CLH阻塞隊列。
CLH阻塞隊列:是一個虛擬雙向隊列,不存在實際的隊列,僅記錄節點之間的關聯關係。
實際上AQS就是基於CLH隊列,用volicate修飾共享變量state,線符程通過CAS去修改狀態符。成功則獲取鎖成功,失敗則進入等待隊列。
看這個圖就清除多了。
11.爲什麼要用線程池?
當併發數量多的時候,頻繁的創建和銷燬線程費時間,所以採用線程池,讓每個線程可以多次使用,減少消耗。
12.線程池的種類以及自定義線程池?
java提供了構建線程池,使線程池使用變得簡單起來了。
public ThreadPoolExecutor(
//初始線程數
int corePoolSize,
//最大線程個數
int maximumPoolSize,
//生存時間,超過初始線程個數以後的線程池使用後的銷燬時間
long keepAliveTime,
//時間單位
TimeUnit unit,
//隊列內部的使用數據結構
BlockingQueue<Runnable> workQueue)
1>.單線程池(newSingleThreadExecutor)
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
這個線程池可以清楚的看到初始線和最大線程都只有1個線程。
2>.定長線程池(newFixedThreadPool)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
可以看到最大和初始化的線程數一樣。適合任務量比較固定且時間消耗比較長的
3>.可緩存線程池(newCachedThreadPool)
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
這個線程數量基本可以無限加,但是沒用後60s之後就消失了。比較以用於任務量大但是消耗時間比較小的。
4>.大小無限制的線程池(newScheduledThreadPool)
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
這個線程池也時固定大小的,構造方法裏邊指定,最大線程數也是無限加,非核心線程創建完以後就銷燬。但是這個線程池使用的DelayedWorkQueue,意味着他會把任務排序,執行完以後循環。適用於定時任務和固定週期的任務。
自定義線程池根據業務需求使用ThreadPoolExecutor去創建,
這些可能也不全,而且理解也不夠透徹,併發編程的水太深了。