天天看點

并發程式設計之線程共享和協作(一)一、基礎概念二、 線程之間的共享三、 線程間的協作

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

繼續閱讀