更多Android架構進階視訊學習請點選: https://space.bilibili.com/474380680
本篇文章将從以下幾個内容來闡述線程共享和協作:
[基礎概念之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/474380680]參考:
https://blog.csdn.net/m0_37661458/article/details/90692419 https://www.cnblogs.com/codetree/p/10188638.html https://blog.csdn.net/aimashi620/article/details/82017700