更多Android架構進階視頻學習請點擊:https://space.bilibili.com/47...
本篇文章將從以下幾個內容來闡述線程共享和協作:
[基礎概念之CPU核心數、線程數,時間片輪轉機制解讀]
[線程之間的共享]
[線程間的協作]
一、基礎概念
CPU核心數、線程數
兩者的關係:cpu的核心數與線程數是1:1的關係,例如一個8核的cpu,支持8個線程同時運行。但在intel引入超線程技術以後,cpu與線程數的關係就變成了1:2。此外在開發過程中並沒感覺到線程的限制,那是因爲cpu時間片輪轉機制(RR調度)的算法的作用。什麼是cpu時間片輪轉機制看下面1.2.
CPU時間片輪轉機制
含義就是:cpu給每個進程分配一個“時間段”,這個時間段就叫做這個進程的“時間片”,這個時間片就是這個進程允許運行的時間,如果當這個進程的時間片段結束,操作系統就會把分配給這個進程的cpu剝奪,分配給另外一個進程。如果進程在時間片還沒結束的情況下阻塞了,或者說進程跑完了,cpu就會進行切換。cpu在兩個進程之間的切換稱爲“上下文切換”,上下文切換是需要時間的,大約需要花費5000~20000(5毫秒到20毫秒,這個花費的時間是由操作系統決定)個時鐘週期,儘管我們平時感覺不到。所以在開發過程中要注意上下文切換(兩個進程之間的切換)對我們程序性能的影響。
二、 線程之間的共享
synchronized內置鎖
線程開始運行,擁有自己的棧空間,就如同一個腳本一樣,按照既定的代碼一步一步地執行,直到終止。但是,每個運行中的線程,如果僅僅是孤立地運行,那麼沒有一點兒價值,或者說價值很少,如果多個線程能夠相互配合完成工作,包括數據之間的共享,協同處理事情。這將會帶來巨大的價值。
Java支持多個線程同時訪問一個對象或者對象的成員變量,關鍵字synchronized可以修飾方法或者以同步塊的形式來進行使用,它主要確保多個線程在同一個時刻,只能有一個線程處於方法或者同步塊中,它保證了線程對變量訪問的可見性和排他性,又稱爲內置鎖機制。
volatile 關鍵字
volatile保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。
private volatile static boolean ready;
private static int number;
不加volatile時,子線程無法感知主線程修改了ready的值,從而不會退出循環,而加了volatile後,子線程可以感知主線程修改了ready的值,迅速退出循環。但是volatile不能保證數據在多個線程下同時寫時的線程安全,參見代碼:
thread-platformsrccomchjthreadcapt01volatilesNotSafe.java
volatile最適用的場景:一個線程寫,多個線程讀。
線程私有變量 ThreadLocal
+ get() 獲取每個線程自己的threadLocals中的本地變量副本。
+ set() 設置每個線程自己的threadLocals中的線程本地變量副本。
ThreadLocal有一個內部類ThreadLocalMap:
public T get() {
Thread t = Thread.currentThread();
//根據當前的線程返回一個ThreadLocalMap.點進去getMap
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(t)方法發現其實返回的是當前線程t的一個內部變量ThreadLocal.ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
//由此可以知道,當調用ThreadLocal的get方法是,其實返回的是當前線程的threadLocals(類型是ThreadLocal.ThreadLocalMap)中的變量。調用set方法也類似。
//舉例一個使用場景
/**
* ThreadLocal使用場景:把數據庫連接對象存放在ThreadLocal當中.
* 優點:減少了每次獲取Connection需要創建Connection
* 缺點:因爲每個線程本地會存放一份變量,需要考慮內存的消耗問題。
* @author luke Lin
*
*/
public class ConnectionThreadLocal {
private final static String DB_URL = "jdbc:mysql://localhost:3306:test";
private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>(){
protected Connection initialValue() {
try {
return DriverManager.getConnection(DB_URL);
} catch (SQLException e) {
e.printStackTrace();
}
return null;
};
};
/**
* 獲取連接
* @return
*/
public Connection getConnection(){
return connectionHolder.get();
}
/**
* 釋放連接
*/
public void releaseConnection(){
connectionHolder.remove();
}
}
//解決ThreadLocal中弱引用導致內存泄露的問題的建議
+ 聲明ThreadLoal時,使用private static修飾
+ 線程中如果本地變量不再使用,即使使用remove()
三、 線程間的協作
wait() notify() notifyAll()
//1.3.1通知等候喚醒模式
//1)等候方
獲取對象的鎖
在循環中判斷是否滿足條件,如果不滿足條件,執行wait,阻塞等待。
如果滿足條件跳出循環,執行自己的業務代碼
//2)通知方
獲取對象的鎖
更改條件
執行notifyAll通知等等待方
//1.3.2
//wait notify notifyAll都是對象內置的方法
//wait notify notifyAll 都需要加synchronized內被執行,否則會抱錯。
//執行wait方法是,會讓出對象持有的鎖,直到以下2個情況發生:1。被notify/notifyAll喚醒。2。wait超時
//1.3.3 舉例使用wait(int millis),notifyAll實現一個簡單的線城池超時連接
/*
* 連接池,支持連接超時。
* 當連接超過一定時間後,做超時處理。
*/
public class DBPool2 {
LinkedList<Connection> pools;
//初始化一個指定大小的新城池
public DBPool2 (int poolSize) {
if(poolSize > 0){
pools = new LinkedList<Connection>();
for(int i=0;i < poolSize; i++){
pools.addLast(SqlConnectImpl.fetchConnection());
}
}
}
/**
* 獲取連接
* @param remain 等待超時時間
* @return
* @throws InterruptedException
*/
public Connection fetchConn(long millis) throws InterruptedException {
// 超時時間必須大於0,否則拋一場
synchronized (pools) {
if (millis<0) {
while(pools.isEmpty()) {
pools.wait();
}
return pools.removeFirst();
}else {
// 超時時間
long timeout = System.currentTimeMillis() + millis;
long remain = millis;
// 如果當前pools的連接爲空,則等待timeout,如果timeout時間還沒有返回,則返回null。
while (pools.isEmpty() && remain > 0) {
try {
pools.wait(remain);
remain = timeout - System.currentTimeMillis();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Connection result = null;
if (!pools.isEmpty()) {
result = pools.removeFirst();
}
return result;
}
}
}
/**
* 釋放連接
*/
public void releaseConn(Connection con){
if(null != con){
synchronized (pools) {
pools.addLast(con);
pools.notifyAll();
}
}
}
}
sleep() yield()
join()
面試點:線程A執行了縣城B的join方法,那麼線程A必須等到線程B執行以後,線程A纔會繼續自己的工作。
wait() notify() yield() sleep()對鎖的影響
面試點:
線程執行yield(),線程讓出cpu執行時間,和其他線程同時競爭cup執行機會,但如果持有的鎖不釋放。
線程執行sleep(),線程讓出cpu執行時間,在sleep()醒來前都不競爭cpu執行時間,但如果持有的鎖不釋放。
notify調用前必須持有鎖,調用notify方法本身不會釋放鎖。
wait()方法調用前必須持有鎖,調用了wait方法之後,鎖就會被釋放。當wait方法返回的時候,線程會重新持有鎖。
更多Android架構進階視頻學習請點擊:[https://space.bilibili.com/47...]
參考:https://blog.csdn.net/m0_3766...
https://www.cnblogs.com/codet...
https://blog.csdn.net/aimashi...