談談我所熟悉的java中的多線程

前段時間讀了《Java併發編程實戰》感覺受益良多,以此博客來記錄下學習的知識點,以及感受


首先什麼是進程和線程

1.進程:計算機系統上運行的一個最小的獨立的單元
2.線程:是進程裏面運行的一個最新的單元,是cpu調度分配的最小單元

線程的生命週期

1.如圖所示
這裏寫圖片描述

2.狀態描述

  • new新建創建線程
  • start是線程處於就緒狀態,等待cpu分配時間片
  • run獲得cpu時間片,線程處於運行狀態
  • join等待某個線程執行完畢,自己線程再執行
  • sleep線程休眠一段時間自動運行
  • yeild讓出時間片,等待cpu重新分配
  • wait線程處於等待狀態,等待其他線程喚醒
  • notify喚醒線程,使得線程處於就緒狀態
    (注:wait和notify是Object的方法,推薦使用java.util.conncurren下的Condition類)

線程的創建方法

  • 繼承Thread類
  • 實現Runnable接口(推薦使用,便於共享同一個變量,符合面向接口編程的思想)

多線程相關的知識點

  • 線程私有的:
    pc計數器、棧內存

  • 線程共享的
    堆內存、方法區
    也就是共享有狀態的單例,以及類的靜態變量

所以多線程併發問題都是不安全的操作了這兩個共享變量引起的

引起不安全的線程操作的原因

  1. 原子性
    主要就是i++的操作
    1.1. 競態條件
    共享變量作爲了判斷條件,比如單例模式的實現就存在競態條件
    1.2. 複合操作
    非原子性操作
  2. 可見性
    工作內存:線程私有
    主內存:線程共享
    步驟將主內存的數據拿入棧中,直到出棧的時候寫入主內存

使得線程安全的操作

  • 原子性:加鎖,採用synchronized或者lock
  • 可見性:volatile,作用直接從主內存中取數據和操作數據,防止指令重排序

鎖優化技術

  • 鎖範圍優化:主要是鎖的顆粒度變小,例如單例:

    private CommonSingleton obj;

    private CommonSingleton() {

    }

    public synchronized CommonSingleton newSingletonInstance() {
        if (obj == null) {
            obj = new CommonSingleton();
        }

        return obj;
    }
}

鎖範圍變小的單例:

public class SafeSingleton {

    private SafeSingleton obj;

    private SafeSingleton() {

    }

    public SafeSingleton newSingletonInstance() {
        if (obj == null) {
            synchronized (SafeSingleton.class) {
                if (obj == null) {
                    obj = new SafeSingleton();
                }

            }
        }

        return obj;
    }
}
  • 鎖分段技術:具體可以參考HashTable和ConcurrentHashMap,後這就是多個HashTable,加鎖鎖住整個對象,而是鎖住一部分
  • 讀寫鎖分離:java.util.concurrent.locks.ReentrantReadWriteLock

線程之間的通信

  • Object對象自帶的wait(),sleep(),notify(),yeild()
  • jdk1.4後支持的java.util.concurrent下的工具

  • Condition:負責線程的等待和喚醒

  • Semaphere:控制線程執行的數量
  • CyclicBarrier:達到一定數目一起執行
  • CountDownLatch:降到0一起執行
  • Exchanger:線程之間交換數據

線程池技術

  1. 線程池產生的原因:線程的過程主要分爲三塊,線程創建T1,線程執行T2,線程銷燬T3,由於頻繁的創建銷燬線程比較耗費時間和佔用系統的內存,所以產生了線程池,由線程池來負責T1和T3的操作,實現線程的複用

    常用的線程池Executors來創建

  2. newFixedThreadPool:固定長度的線程池,緩存隊列是無界隊列LinkedBlockingQueue

  3. newCachedThreadPool:無長度的線程池,沒有緩存隊列,用的是SynchronousQueue
  4. newSingledThreadPool:獨立的線程池,和newFixedThreadPool的區別是corePoolSize和maximumPoolSize的值都是1
  5. newSingleThreadScheduledExecutor:時間調度的線程池,用的是DelayedWorkQueue

線程池中的關鍵的參數

  1. corePoolSize:核心的池數量,也就是初始的
  2. maximumPoolSize:最大線程池數量
  3. keepalivedTime:多久時間判斷線程是否已經執行結束
  4. timeUnit:時間單位
  5. workQueue:緩存的線程隊列
  6. ThreadFactory:創建線程的工廠
  7. RejectedExecutionHandler:拒絕策略
  8. HashSet workers:運行的線程集
    ## 線程池的運行步驟 ##
    首先線程池裏面默認是沒有線程的,當用戶執行execute,判斷當前運行的線程數量是否小於corePoolSize,小於則交由workers去直接創建線程,並將當前運行線程加1,如果大於了corePoolSize,則將線程放入workQueue中,等待workers裏面有線程空閒出來,則去取,如果阻塞隊列裏面的超過maximumPoolSize,則由RejectedExecutionHandler去拒絕接收線程
    public static ExecutorService threadPool = new ThreadPoolExecutor(MAX_QUERY_THREAD_NUM, 200, 0L,
            TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1024), new ThreadFactoryBuilder().build(),
            new ThreadPoolExecutor.AbortPolicy());

注:並將線程池弄成單例,放在獨立的類裏面,防止簡歷多個線程池,造成浪費

Callable和Future

  • 通過線程池的submit方法執行,與Runable不同的地方就是Callable的call方法會返回一個對象,可用take()方法去獲取,take()方法裏面可以控制時間
  • CompleteService阻塞隊列,控制線程一起返回一起執行

Synchronized的實現原理

  • 在java堆內存中,其實對象裏面有個對象頭的概念,對象頭裏面存儲着鎖的相關信息
  • 輕量級鎖
  • 偏移鎖
  • 自旋鎖
  • 重量級鎖
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章