java多線程與鎖

java併發學習

併發相關知識

併發相關知識

併發(Concurrency),並行(Parallelism)

併發:多項任務,交替執行

並行:多項任務,同時執行

同步(Synchronous),異步(Asynchronous)

描述的是針對某個調用 獲取返回結果的方式:是同步等待,還是異步通知

同步:調用某項方法時,等待方法返回結果

異步:調用後馬上返回,結果計算完後,通知調用者

阻塞(blocking),非阻塞(non-blocking)

描述的是多線程之間的相互影響

阻塞:一個線程佔用了臨界資源,其他線程必須等待這個線程釋放資源

非阻塞:訪問被其他線程佔用的臨界資源時, 不會阻塞等待,而立即返回

臨界區

表示公共資源,多個線程訪問或修改同一個資源

多線程競爭鎖導致會問題

  1. 死鎖:所有線程都不能動
  2. 飢餓鎖:某個線程一直無法獲取所需的資源
  3. 活鎖:線程秉承謙讓的原則,主動釋放給他人使用,這樣可能會導致資源在兩個線程中跳動,而沒有一個線程正常執行

併發級別

阻塞

一個線程會阻塞在 獲取資源的步驟中,直到其他線程釋放該資源,synchronized的鎖爲阻塞級別

無飢餓

如果獲取鎖是公平的,各個線程排隊獲取鎖,則該鎖是無飢餓的

無障礙

  1. 最弱的非阻塞調度
  2. 兩個線程訪問同一個臨界區,都不會被對方所阻塞,一旦檢測到某一方把數據改動了,則所有線程操作全部回滾
  3. 阻塞的控制方式是 悲觀策略,假定兩個線程之間很可能發生衝突,而非阻塞的調度是樂觀的策略,認爲多個線程不會發生衝突,或者概率不大,一旦發生衝突,就應該回滾

無鎖

  1. 要求有一個線程可以在有限步內完成操作
  2. 當所有線程都能嘗試對臨界區訪問,但只有一個線程能 進入臨界區,其他的線程會不斷嘗試

無等待

  1. 要求所有線程必須在有限步內完成
  2. 典型的無等待結構是 RCU(read-copy-update),讀無等待,更新時,先取得副本更新,然後適時寫回

並行的兩個重要定律

Amdahl定律

  1. 定義了串行系統並行化的加速比的計算公式,和理論上限

=/F:T1:Tn:nTn=T1(F+1n(1F))=T1Tn=1F+1n(1F) 加速比 = 優化前系統耗時 / 優化後系統耗時\\F:爲系統串行比例\\T_1:爲一個處理器的耗時\\T_n:爲n個處理器優化後的耗時\\T_n = T_1(F+\frac{1}{n}*(1-F))\\加速比 = \frac{T_1}{T_n} = \frac{1}{F+\frac{1}{n}*(1-F)}

  1. 由公式可分析出
    1. CPU處理器數量趨近於無窮,那麼加速比與系統串行率成反比
    2. 如果系統串行率爲50%,則系統最大加速比爲2

Gustafson定律

a:,b:,n=a+b=a+nb=a+nba+b=F=aa+b=a+nba+b=aa+b+n(a+ba)a+b=F+n(1F)=nF(n1) a:串行時間,b:並行時間,n處理器個數\\ 實際執行時間 = a+b\\ 總執行時間 = a+n*b\\ 加速比= \frac{a+n*b}{a+b}\\ 串行比例 = F = \frac{a}{a+b}\\ 加速比 = \frac{a+n*b}{a+b} = \frac{a}{a+b}+\frac{n*(a+b-a)}{a+b}=F+n*(1-F)=n-F*(n-1)

  1. 兩個定律的不同點

    1. Amdahl定律側重於 當 總任務一定時, 當串行比例一定時,加速比是有上線的
    2. Gustafson定律側重於 不管F的值有多高,只要 n足夠大,有足夠的時間和 工作量,就能達到某個加速比

java多線程併發原則

原子性 Atomicity

函數調用過程中 不可被其他線程打斷,要麼成功,要麼失敗

可見性 visibility

對某一線程修改了某一個共享變量,其他線程能夠立刻知道

有序性 ordering

  1. 在程序編譯時可能 有指令重排:通過指令重排 減少CPU流水線指令的停頓
  2. 線程重排原則
    1. 程序順序原則:一個線程內保證語義的串行性,不保證並行性
    2. volatile變量的寫 先發生於讀
    3. 鎖規則:解鎖必然發生在 加鎖前
    4. 傳遞性: a 先於b,b先於c,a必然先於c
    5. 線程start方法優先於它的每一個動作
    6. 所有操作先於 線程的終結
    7. 中斷先於 被中斷線程的代碼
    8. 對象的構造函數執行,結束先於finalize方法

java並行程序基礎

線程狀態變更圖

線程狀態詳細圖

在這裏插入圖片描述

java線程設計狀態圖

在這裏插入圖片描述

線程基本操作

新建線程:start

Thread.start()

線程停止:stop

  1. Thread.stop() :線程放棄一切工作,馬上退出,這樣會導致很多隱患
  2. 在線程內部設置停止標識:有線程自己決定在哪地方退出

線程中斷:interrupt

  1. java已經實現中斷標識,用於線程自行決定在哪裏退出

    1. 判斷是否中斷:Thread.isInterrupted()
    2. 判斷是否中斷並清除中斷標記:static Thread.interrupted()
    3. 發出中斷:Thread.interrupt()
  2. Thread.sleep() 捕捉到中斷之後,會清除中斷標記

  3. code

    package com.weisanju;
    public class InterruptedTest {
        public static class AThread implements  Runnable{
            @Override
            public void run() {
                while(true){
                    if(Thread.currentThread().isInterrupted()){
                        System.out.println("已被中斷");
                        break;
                    }
                    System.out.println(1);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        Thread.currentThread().interrupt();
                    }
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new AThread());
            thread.start();
            Thread.sleep(1000);
    
            thread.interrupt();
        }
    }
    

線程等待:wait,notify

  1. 當線程執行到該行代碼時, 且該對象爲持有鎖的對象,則線程進入等待狀態,等待其他線程調用該對象的notify
  2. 這樣就實現了多線程的簡單通信
  3. code
package com.weisanju;

public class WaitNotifyTest {
    private  static  Object obj = new Object();

    public static class Athread implements  Runnable{
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            synchronized (obj){
                System.out.println("A acquire lock");
                System.out.println("A通知B");
                obj.notify();
                System.out.println("通知完畢");
            }
        }
    }
    public static class Bthread implements  Runnable{
        @Override
        public void run() {
            synchronized (obj){
                System.out.println("B acquire lock");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("B被喚醒");
            }
        }
    }

    public static void main(String[] args) {
        new Thread(new Athread()).start();
        new Thread(new Bthread()).start();
    }
}

掛起與繼續執行:suspend,resume

  1. suspend 掛起當前線程,但不釋放鎖,與資源, 直到調用resume才恢復執行
  2. 不推薦使用,推薦使用wait,notify

等待線程結束:join and yield

  1. join : 線程join 實際調用 Thread.wait() 方法,
  2. 當線程結束時,會通知所有等待在線程對象的其他線程
  3. yield 讓出CPU,重新與其他線程競爭CPU調度

volatile關鍵字

  1. 修飾變量
  2. 告知各個線程,取變量值時,從主內存中取,不要從副本取

線程組

package com.weisanju;

public class ThreadGroupTest {
    public static class AThread implements  Runnable{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        ThreadGroup threadGroup = new ThreadGroup("xjq");
        Thread t1 = new Thread(threadGroup,new AThread(),"t1");
        Thread t2 = new Thread(threadGroup,new AThread(),"t2");
        t1.start();
        t2.start();
        threadGroup.list();
        System.out.println(threadGroup.activeCount());
        System.out.println(threadGroup.activeGroupCount());
    }
}

守護線程

  1. 線程分爲用戶線程 ,守護線程

  2. 當用戶線程執行完畢之後, 守護線程會自行退出

  3. 守護線程一般完成系統性服務,例如垃圾回收,JIT線程

  4. 代碼

    package com.weisanju;
    
    public class DeamonTest {
        public static class  Athread implements  Runnable{
    
            @Override
            public void run() {
                while(true){
                    System.out.println(1);
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread t = new Thread(new Athread());
            t.setDaemon(true);
            t.start();
            Thread.sleep(2000);
        }
    }
    
    

線程優先級

  1. Thread.MAX_PRIORITY = 10
  2. Thread.NORM_PRIORITY = 5
  3. Thread.MIN_PRIORITY = 1

jdk併發包

可重入鎖

  1. 重入鎖可以完全替代synchronized 關鍵字, jdk1.5 之間重入鎖性能遠遠好於 synchronized 從1.6開始,jdk在synchronized 做了大量優化,使得兩者性能差距並不大

  2. 特性

    1. 可重入性質: 一個線程可以連續兩次獲得鎖, 但相應的得釋放兩次鎖
    ReentryantLock lock1 = new ReentryantLock();
    lock1.lock()
    lock1.lock()
    lock1.unlock()	
    lock1.unlock()	
    
    1. 可中斷性質
      1. 線程在嘗試獲取鎖時,可被打斷,並被打斷後,釋放相應的鎖,讓其他線程獲取鎖
      2. 案例 : 線程a, 線程b ,a先得到鎖1,然後請求鎖2,b先得到鎖2,然後請求鎖1
      3. 代碼
    package com.weisanju;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class DeadLock {
        private static ReentrantLock lock1= new ReentrantLock();
        private static ReentrantLock lock2= new ReentrantLock();
    
        public static class  ThreadTest implements  Runnable{
            private char name;
    
            public ThreadTest(char name) {
                this.name = name;
            }
    
            @Override
            public void run() {
                if(name == 'A'){
                    try {
                        lock1.lockInterruptibly();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        Thread.sleep(1000);
                        lock2.lockInterruptibly();
                        System.out.println("A 得到鎖了");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        if(lock1.isHeldByCurrentThread()){
                            lock1.unlock();
                        }
                        if(lock2.isHeldByCurrentThread()){
                            lock2.unlock();
                        }
                    }
    
                }else{
                    try {
                        lock2.lockInterruptibly();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    try {
                        Thread.sleep(1000);
                        lock1.lockInterruptibly();
                        System.out.println("B 得到鎖了");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }finally {
                        if(lock1.isHeldByCurrentThread()){
                            lock1.unlock();
                        }
                        if(lock2.isHeldByCurrentThread()){
                            lock2.unlock();
                        }
                    }
                }
            }
        }
    
        public static void main(String[] args) {
            Thread ta = new Thread(new ThreadTest('A'));
            Thread tb = new Thread(new ThreadTest('B'));
    
            ta.start();
            tb.start();
    
            try {
                Thread.sleep(4000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            tb.interrupt();
    
        }
    }
    
    
    1. 超時性質
      1. tryLock():嘗試獲取鎖,獲取不成功則馬上返回
      2. tryLock(long mili):嘗試獲取鎖,並等待指定時間段
    2. 公平鎖
      1. 鎖的申請遵循 先到先到,支持排隊
      2. public ReentrantLock(boolean fair)
      3. 實現公平鎖,系統需要維護一個有序隊列,實現成本較高,性能太低
      4. 根據系統的調度,一個線程會傾向於再次獲取已經持有的鎖,這種鎖分配是高效的

Conditional條件等待

  1. 與 synchronized 配合 wait,notify使用類似 , condition 配合與 Reentryant鎖使用實現線程間通信
  2. 代碼
package com.weisanju;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionalTest {
    private  static  int flag =0;
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition= lock.newCondition();
    private  static class  AThread implements Runnable{
        @Override
        public void run() {
            lock.lock();
            System.out.println("正等待條件發生");
            try {
                condition.await();
                System.out.println(flag);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock.unlock();
            }

        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(new AThread()).start();
        flag = 666;
        Thread.sleep(200);
        lock.lock();
        System.out.println("已經獲取鎖");
        Thread.sleep(1000);

        condition.signal();
        lock.unlock();
    }
}

信號量

  1. API
    1. 構造函數:public Semaphore(int premits)
    2. 邏輯方法
      1. acquire|acquireUninterruptible()|tryAcquire()
      2. release
  2. 例子:省略

讀寫鎖

  1. 讀寫操作互斥表

    不阻塞 阻塞
    阻塞 阻塞
  2. API

    1. ReentrantReadWriteLock
    2. lock.readLock(),lock.writeLock()

倒計時

  1. API
    1. 構造函數:public CountDownLatch(int count)
    2. 邏輯操作
      1. 計時器減1:CountDownLatch.countDown()
      2. 等待計時器歸0:``CountDownLatch.await();`
      3. 獲取計數器:CountDownLatch.getCount()

CyclicBarrier循環柵欄

  1. 每當有 parties 個 到達 wait 點時, 則執行barrierAction

  2. APi

    1. 構造函數:public CyclicBarrier(int parties, Runnable barrierAction)

    2. await:等待

    3. 一個線程在等待時被打斷, 則其他線程拋出BrokenBarrierException,該線程拋出:InterruptedException

    4. code

      package com.weisanju;
      
      import java.util.concurrent.BrokenBarrierException;
      import java.util.concurrent.CyclicBarrier;
      
      public class CyclicBarrierTest {
          private static CyclicBarrier barrier = new CyclicBarrier(5,new BarrierRun(false));
          public  static  class Solider implements  Runnable{
              private int i;
      
              public Solider(int i) {
                  this.i = i;
              }
      
              @Override
              public void run() {
                  try {
                      barrier.await();
      
                      Thread.sleep(1000);
                      System.out.println("士兵"+i+"完成任務");
                      barrier.await();
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  } catch (BrokenBarrierException e) {
                      e.printStackTrace();
                  }
              }
          }
          public  static  class BarrierRun implements  Runnable{
              private boolean flag ;
      
              public BarrierRun(boolean flag) {
                  this.flag = flag;
              }
      
              @Override
              public void run() {
                  if (flag) {
                      System.out.println("任務完成");
                  }else{
                      System.out.println("集合完畢");
                      flag = true;
                  }
              }
          }
      
          public static void main(String[] args) {
              int n = 5;
      
              for (int i = 0; i < n; i++) {
                  System.out.println("士兵報數:"+i);
                  new  Thread(new Solider(i)).start();
              }
          }
      }
      
      

線程阻塞工具類

  1. API

    1. LockSupport.unpack(Object),LockSupport.pack(Thread)
    2. 類似於 值爲1的信號量 操作
    3. unpack操作發生在pack操作之前,unpack使得許可可用,pack消耗許可
    4. 不需要獲取鎖
    5. 爲每一個線程都擁有一個許可證
    6. 被打斷之後正常返回,可以通過 Thread.isInterrputed
    7. unpack(Object):object 爲日誌打印時的對象
  2. code

    package com.weisanju;
    
    import java.util.concurrent.locks.LockSupport;
    
    public class LockSupportTest {
        public static  class AThread implements Runnable{
            @Override
            public void run() {
                LockSupport.park();
                if(Thread.currentThread().isInterrupted()){
                    System.out.println("被打斷了");
                    return;
                }
                System.out.println("正常運行");
            }
        }
    
        public static void main(String[] args) {
            Thread t1 = new Thread(new AThread());
            Thread t2 = new Thread(new AThread());
    
            t1.start();
            t2.start();
            t1.interrupt();
            LockSupport.unpark(t2);
        }
    }
    

線程池

  1. API

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 
    
  2. 參數解釋

    1. corePoolSize:線程活躍數量
    2. maximumPoolSize:最大線程數量
    3. keepAliveTime:超過corePoolSize部分,空閒的線程存活時間
    4. TimeUnit:時間單位
    5. BlockingQueue:任務隊列,提交但未運行
    6. ThreadFactory:創建線程的工廠
    7. handler:任務太多來不及處理的處理策略
  3. blockingQueue分三種

    1. 直接提交的隊列
      1. 新任務提交給線程池時,如果線程數量<maximumPoolSize,則直接創建,否則拒絕
      2. SynchronousQueue
    2. 有界任務隊列
      1. ArrayBlockingQueue
      2. 若已有線程數量 小於 corePoolSize ,則創建新的線程,直接運行
      3. 若大於corePoolSize ,則加入等待隊列
      4. 若等待隊列已滿,且當前線程數量小於maximumPoolSize則新建線程
      5. 若當前線程數量已等於maximumPoolSize,則執行拒絕策略
    3. 無界任務隊列
      1. LinkedBlockingQueue
      2. 若已有線程數量 小於 corePoolSize ,則創建新的線程,直接運行
      3. 若大於corePoolSize ,則加入等待隊列
      4. 無界隊列會一直增長 直到內存耗盡
    4. 優先任務隊列:特殊的無界隊列
      1. PriorityBlockingQueue:
  4. 內置四種拒絕策略

    1. AbortPolicy: 直接拋出異常
    2. CallerRunsPolicy:直接在調用者線程中運行當前被丟棄的任務
    3. DiscardOldestPolicy:丟棄最老的請求,也就是即將被執行的,並嘗試再次提交當前任務
    4. DiscardPolicy:丟棄該任務
  5. ThreadFactory:自定義線程創建

    1. ThreadFactory是一個接口,只有 Thread newThread(Runnable r)接口
  6. 擴展線程池

    1. ThreadPoolExecutor 可擴展線程池
    2. code
    package com.weisanju;
    
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;
    
    public class ThreadPoolTest {
        public static void main(String[] args) {
            ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(20)) {
                protected void beforeExecute(Thread t, Runnable r) {
                    System.out.println("線程" + t.getName() + "開始運行");
                }
                protected void afterExecute(Runnable r, Throwable t) {
                    System.out.println( r.toString()+ "結束運行");
                }
    
                protected void terminated() {
                    System.out.println("線程池退出");
                }
            };
            executor.execute(()->{
                System.out.println("helloWorld");
            });
            executor.shutdown();
        }
    }
    
    
  7. 線程池的大小 引申自<< Java Concurrency in practice>>

Ncpu=CpuUcpu=cpu使,0<<Ucpu<<1wc=Nthreads=NcpuUcpu(1+wc) Ncpu = Cpu數量\\ Ucpu = cpu的使用率, 0 << Ucpu << 1\\ \frac{w}{c} = \frac{等待時間}{計算時間}\\ Nthreads = Ncpu * Ucpu * (1+\frac{w}{c})

forkAndJoin庫

併發容器

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