多線程筆記

来源:https://www.cnblogs.com/LiuZhen/archive/2022/03/18/15989709.html
-Advertisement-
Play Games

Thread 線程狀態:新建(new),就緒(start),運行(run),阻塞,死亡 start 方法內部調用了 run 方法,start 會開啟線程,run 只是內部方法; sleep 會占用鎖,休眠時間到重新運行,wait 會釋放鎖; stop 停止線程比較暴力,對鎖的對象進行強制解鎖,線程資 ...


Thread

线程状态:新建(new),就绪(start),运行(run),阻塞,死亡

start 方法内部调用了 run 方法,start 会开启线程,run 只是内部方法;

sleep 会占用锁,休眠时间到重新运行,wait 会释放锁;

stop 停止线程比较暴力,对锁的对象进行强制解锁,线程资源因此得不到正常释放;

interrupt 不会立马停止线程,只能中断阻塞状态的线程,可以捕获到一个异常来处理,加上标识判断是否中断;

join 等待该线程完成后,才能继续用下运行;

yield 线程让步,让自己或者其他线程运行,并不能保证其它线程就一定能获得执行权;

wait 进入阻塞状态,释放锁,需要在synchronized使用(获取锁后);

notify 唤起线程(随机),notifyAll唤起所有线程,释放锁,需要在synchronized使用(获取锁后),调用notify和wait的必须是作用同一个对象;

创建方式

//第一种
new Thread().start();
//第二种
new Thread(Runnable实现类).start();

ThreadLocal

线程局部变量,为线程提供变量副本,每个线程改变副本后不会对其它线程造成影响。

内部通过 ThreadLocalMap 来存储值,每个Thread类里面会有一个 ThreadLocalMap 内部变量,可以直接使用,而 ThreadLocalMap 内部使用数组来存储。

public class ThreadLocal<T> {

    static class ThreadLocalMap {

      //The table, resized as necessary. table.length MUST always be a power of two.
      private Enrty[] table;
      static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
        //key为ThreadLocal当前对象,value就是我们存入的值
        Entry(ThreadLocal<?> k, Object v) {
          super(k);
          value = v;
        }
    }
  }

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
}    

public class Thread implements Runnable {
  /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

存储

想要存入的数据实际上并没有存到 ThreadLocal 中去,而是以这个 ThreadLocal 实例作为key存到了当前线程中的一个Map(没有链表结构)中去了;

如果发生hash冲突,会线性向后查找,一直找到 Entry 为 null 的时候添加,key相同则直接更新值;

过程碰到回收的过期数据,会进行探测清理操作(replaceStaleEntry()),执行方法遍历数组,直到碰到null停止探测,然后将过期数据清空,清空后把后面的数据往前移,提高后面查询效率。

查询

线性查询,判断hash值,如果存在相同hash值(发生过hash碰撞),则判断是否为相同key,如果不是继续往后迭代查找,如果一致直接返回value;

查询也会触发探测清理操作。

GC回收

ThreadLocalMap 内部使用 Entry extends ThreadLocal 弱引用的数组来存储,但是value依然会导致残留,根本解决方案有两种

1.需要remove这个Entry;

2.停止当前线程(map也会跟着gc);

第二种方案不太好控制,最好使用第一种。

使用弱引用的原因

ThreadLocalMap 自己有探测清理操作,所以只要当前线程在运行,调用查询增删方法也会把value删除,避免内存泄漏OOM

Handler- 线程通讯工具

Handler

Handler 通过 Looper 的 prepare 方法创建Looper对象,存放在 ThreadLocal 中,保证每个线程的Looper是独立的;

Looper 会持有当前线程的引用,并且创建一个 MessageQueue 队列存放消息 Message;

队列 MessageQueue  为单向链表(增删效率高),按 Message 中的 when(处理时间)字段排序;

Handler 负责发送和处理消息,在发送消息( enqueueMessage() )的时候会把自己赋值给 Message 中的 target 字段,在把 Message放入 MessageQueue 中。

Handler 将消息 message 发送到队列,Looper会调用loop方法开启一个死循环,读取队列消息,最后拿到 Handler 实例(target),然后通过 target 调用 dispatchMessage 分发到 Handler 所在线程中的 callback 中。

一个线程只有一个 Looper(包含消息队列),可以有多个 Handler。

public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);//post Runnable
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

内存泄漏

一般发生在延迟消息中,关闭Activity后延迟消息还未发出,MessageQueue就会持有这个Message的引用,而Message的target又持有Handler,Handler如果是内部类,会持有Activity,从而导致Activity无法被回收,引发OOM。

Handler 是内部类,持有 Activity 引用,Activity 被 finish 后 Handler 任务又没处理完,会导致 Activity 无法被回收,需要静态化处理,在调用 removeCallbacksAndMessages 清空消息队列。

Looper

Android启动时就会调用 prepare 方法绑定 Looper 对象,事件都在 Looper 的控制下,ActivityThread的main方法主要就是做消息循环,如果循环停止,应用也会停止。

public static void main(String[] args) {
    Looper.prepareMainLooper();
    ...
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

 

public static void loop() {
    final Looper me = myLooper();
    ...
    final MessageQueue queue = me.mQueue;
    ...
    for (;;) {
        Message msg = queue.next(); // 获取Message对象。
        ...
        try {
            msg.target.dispatchMessage(msg);
        } finally {
            ...
        }
        ...
    }
}    

MessageQueue

单链表结构,时间顺序(when字段),没有消息时,会进入阻塞状态,此时主线程会释放CPU资源进入休眠状态,直到下个消息过来或者事务发生,才会唤醒主线程工作;

如果是带延迟的 message,根据时间顺序插入到 MessageQueue,然后入阻塞状态,如果队列前面有消息,会唤醒处理。

屏障消息

挡住普通消息以此来保证异步消息的优先处理,屏障消息和普通消息的区别在于屏障消息没有 tartget 字段,同样按时间 when 排序,挡住它后面的同步消息的分发;

postSyncBarrier 返回 int 类型,通过这个数值可以撤销屏障消息,并且是私有方法,无法唤醒队列工作。

message

一般通过 obtain 方法创建对象,会从消息池中获取对象,重新赋值然后返回,避免多次创建对象,并且 obtain 方法有同步锁,如果池子中没有对象则new新对象。

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

AsyncTask(Android11将移除该库,推荐用线程池代替)

简化版异步任务,本质是对 Handler+Executors 的封装,内部维护一个长度MAX的队列;

在任务并不会立马将任务提交给线程池,而是等待上一个任务完成后在提交,达到串行的目的,任务完成后利用 Handler 发送消息。

AsyncTask 被声明为Activity的非静态内部类时,会持有引用,容易造成内存泄漏。

private static class SerialExecutor implements Executor {
        final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
        Runnable mActive;

        public synchronized void execute(final Runnable r) {
            mTasks.offer(new Runnable() {
                public void run() {
                    try {
                        r.run();
                    } finally {
                        scheduleNext();
                    }
                }
            });
            if (mActive == null) {
                scheduleNext();
            }
        }

        protected synchronized void scheduleNext() {
            if ((mActive = mTasks.poll()) != null) {
                THREAD_POOL_EXECUTOR.execute(mActive);
            }
        }
    }

线程池 - ExecutorService

Java提供了 Executors 工厂,方便开发者创建,所有的方法返回的都是ThreadPoolExecutorScheduledThreadPoolExecutor这两个类的实例。

newCachedThreadPool

可缓存线程池,如果超过线程池长度则回收空闲线程,若无可回收,则新建线程,因为允许创建的线程数量为MAX,线程过多可能会导致OOM。

newFixedThreadPool

固定长度线程池,可控制最大并发数,超出的线程会在队列中等待,但是 LinkedBlockingQueue 队列长度是MAX,任务多时会存在OOM。

newScheduledThreadPool

 定时线程池,支持定时及周期性的任务执行,但是 LinkedBlockingQueue 队列长度是MAX,任务多时会存在OOM。

newSingleThreadExecutor

单线程的线程池,保证所有任务按照指定顺序执行,但是 LinkedBlockingQueue 队列长度是MAX,可能导致OOM。

 

阿里手册推荐:一般使用自定义线程池 ThreadPoolExecutor 创建,可以更加清楚的知道线程池的运行规则,精确控制池子粒度,工厂的创建也是走的这套流程。

线程个数不是越多越好,线程多了切换上下文时间变多,反而是负担。

上下文切换:线程调用CPU处理任务,但是CPU内核个数比较少,线程调用完会保存状态切换到下一个线程,很消耗时间。

 

CPU任务:一般内存数据计算型任务,主要消耗 CPU 资源,可以将线程数设置为 CPU 核心数+1,CPU任务上下文切换比较频繁,留出一个内核来协调其余因为频繁带来暂停中断等任务。

IO任务:网络,文件读取等,io读取比较耗时,处理io时CPU是不占用的,所以可以配置多一些,一般是两倍内核数线程。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Google Analytics: Universal Analytics增強型電子商務,可以讓運營人員輕鬆地跟蹤用戶在其購物歷程中與產品的互動,包括產品展示、產品點擊、查看產品詳情、將產品添加到購物車、結賬流程、交易以及退款等操作。 目前,Google Analytics: Uniersal An ...
  • 如果我們需要設置一個view在另一個view的右邊緣距離一定距離的地方,利用Masonry這麼寫: offset可以讓我們在設置視圖之間相對位置時起到很大作用,但是也有很多情況下用不到,比如size、center等等,而調查Maronry的底層方法,可以看到和offset類似的方法還有很多: off ...
  • RGB、YUV、HSV和HSL區別和關聯 近期在做的一個需求和顏色轉換有關係,所以本篇將開發過程中比較常見的 四種顏色 進行一番梳理。 一、RGB顏色空間 從我們最常見的RGB顏色出發,RGB分別對應著 Red(紅)、Green(綠)、Blue(藍),也就是我們平時所說的三原色,調整這三種顏色的比例 ...
  • 現在的SIM卡通常具備基站定位、語音通話、簡訊消息、網路流量這四大功能,而在移動端是無法對SIM卡使用基站定位功能的,所以這裡只介紹移動端如何使用SIM卡實現語音通話、簡訊消息、數據流量三個功能。 ##語音通話 Android系統中提供了通話服務,同時自帶系統級應用可以通過該通話服務使用SIM卡的通 ...
  • Android 12(API 31)於2021年10月4日正式發佈,正式版源代碼也於當日被推送到AOSP Android開源項目。截止到筆者撰寫這篇文章時,國內各終端廠商的在售Android設備,已經逐步開啟了Android 12正式版本的更新。當前,對於Android應用開發者來說,Android... ...
  • 最近表弟一直在找實習,經常會問我一些問題,有些問題在沒有經歷過真實工作時是真的不好理解的,所以我開了這個【表弟專欄】,專門為找工作的表弟解決一些疑惑。 這篇文章從電腦發展的角度出發,描述為什麼電腦需要劃分 "主/子線程" 和 "同/非同步",希望這個故事你能喜歡。 開天闢地,電腦"老計"誕生 話 ...
  • 代碼變更溯源 工作時,我們經常會想要查看一個類文件的變更歷史,最常見的場景是:"卧槽,誰改了我的代碼" 新版本的Xcode溯源自我感覺相當難用,所以這裡我們介紹一個工具 SourceTree 來完成這項工作。 將項目工程載入到 SourceTree 當我們把項目工程拖到 SourceTree 之後, ...
  • 針對兒童和老人,可穿戴的智能手錶用處很大。市場也有許多類似的產品,支持接打電話、支付掃碼、定位等功能,屬於新興的商業機會。依托華為品牌,鴻蒙手錶也致力為用戶打造精品的、產品質量佳、可穿戴的智能體驗。對此,HMS Core 定位服務(Location Kit)可以提供三個主要能力,包括融合定位、活動識 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...