併發編程之線程共享和協作(一)

更多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...

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章