併發基礎學習小記

一個簡單的demo

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class CountExample1 {

    // 請求總數
    public static int clientTotal = 5000;
    // 同時併發執行的線程數
    public static int threadTotal = 200;
    public static int count = 0;

    public static void main(String[] args) throws Exception {
    	// 線程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 200個線程併發
        final Semaphore semaphore = new Semaphore(threadTotal);
        // 線程技術,await時線程處於等待狀態直到countDownLatch 爲0
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        count++;
    }
}

以上代碼用於測試線程併發,不瞭解的類就去百度下。

實現線程安全的幾種方式

1、線程原子類Automic

automic一個更爲高效的方式CAS (compare and swap) ,避免了synchronized的高開銷,執行效率大爲提升;適用於併發不是很大的場景

2、synchronize 線程加鎖

線程解鎖前,必須把共享變量的最新值刷新到主內存中
線程加鎖時, 必須將共享變量清空,從主內存中獲取最新的值

3、 volatile 可見性

保證了不同線程對這個變量進行操作時的可見性,即一個線程修改了某個變量的值,這新值對其他線程來說是立即可見的。
volatile 變量的內存可見性是基於內存屏障(Memory Barrier)實現
注意:在對volatile 變量進行操作時不能保證線程時安全的
適用於做線程通信的狀態碼

線程安全策略

1、不可變對象
  • 擋final 修飾變量是,一旦初始化就不能被修改; 修飾對象時,對象的屬性可以修改,但對象的引用不可變
  • 集合對象可以用Collections.unmodifiableXXXX包裝一下變爲不可變對象
2、線程封閉(ThreadLocal)
	從接收請求到返回響應所經過的所有程序調用都同屬於一個線程。將一些非線程安全的變量以ThreadLocal存放,對象所訪問的同一ThreadLocal變量都是當前線程所綁定的。

實驗: 當 ThreadLocal set 賦值一個單例對象時, 再取出對對象內部屬性進行修改,並不能保證線程對象安全;對於基本數據類型, 可保證線程隔離。(待說明

線程安全類和寫法

1、StringBuilder -> StringBuffer
  • StringBuilder 線程不安全的
  • StringBuffer 線程安全的, 每個方法都加有synchronized 鎖
2、SimpleDateFormat -> joda-time
  • SimpleDateFormat 當多線程使用同一個對象時會報錯,
  • 解決辦法:聲明一個局部的對象 joda-time線程安全的,有空多瞭解下
3、HashSet、HashSet、ArrayList 線程不安全的
  • ArrayList -> Vector,Stack
  • HashMap -> HashTable(key、value 不能爲null)
  • Collections.synchronizedXXX(List,Set,Map)
  • 同步容器,影響到性能
4、併發容器 J.U.C.
  • 實現了List接口
  • 內部持有一個ReentrantLock lock = new ReentrantLock();
  • 底層是用volatile transient聲明的數組 array
  • 讀寫分離,寫時複製出一個新的數組,完成插入、修改或者移除操作後將新數組賦值給array
  • ArrayList -> CopyOnWriteArrayList
  • HashSet、TreeSet -> CopyOnWriteArraySet、ConcurrentSkipListSet
  • HashMap、TreeMap -> ConcurrentHashMap、ConcurrentSkipListMap

J.U.C.併發編程

AQS 抽象隊列化同步器(Abustact Queued Synchronizer )

1、CountDownLatch**

可用於線程計數或線程狀態監控
例如: 子線程任務執行完了之後執行主線程任務
注意: 在線程中最好放在final中

final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
countDownLatch.countDown();
countDownLatch.await();
2、Semaphore 信號量
final Semaphore semaphore = new Semaphore(threadTotal);
semaphore.acquire();
semaphore.release();

用於控制線程的並行, 例如線程池

3、CyclicBarrier

描述: 描述所有線程被柵欄擋住了,當都達到時,一起跳過柵欄執行,也算形象。我們可以把這個狀態就叫做barrier
代碼:

@Log4j2
public class CyclicBarrierDemo {

    private static CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

    public static void main(String[] args) throws Exception {
        ExecutorService executors = Executors.newCachedThreadPool();
        for(int i  =0 ;i<10;i++){
            Thread.sleep(1000);
            final int index = i;
            executors.execute(()->{
                try {
                    show(index);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
    }
    public static void show(int i) throws BrokenBarrierException, InterruptedException {
        log.info("正在集合,{}:已經到達",i);
        cyclicBarrier.await();
        log.info("集合完畢,{}:開始跑了",i);
    }
}

輸出:

22:53:40.636 [pool-1-thread-1] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,0:已經到達
22:53:41.622 [pool-1-thread-2] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,1:已經到達
22:53:42.622 [pool-1-thread-3] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,2:已經到達
22:53:43.623 [pool-1-thread-4] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,3:已經到達
22:53:44.625 [pool-1-thread-5] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,4:已經到達
22:53:44.626 [pool-1-thread-4] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完畢,3:開始跑了
22:53:44.626 [pool-1-thread-3] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完畢,2:開始跑了
22:53:44.626 [pool-1-thread-2] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完畢,1:開始跑了
22:53:44.626 [pool-1-thread-1] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完畢,0:開始跑了
22:53:44.626 [pool-1-thread-5] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,36] - 集合完畢,4:開始跑了
22:53:45.625 [pool-1-thread-5] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,5:已經到達
22:53:46.626 [pool-1-thread-1] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,6:已經到達
22:53:47.626 [pool-1-thread-3] INFO  c.r.p.c.J.CyclicBarrierDemo - [show,34] - 正在集合,7:已經到達
4、ReentrantLock 重入鎖

synchronize 是jvm 實現的,是非公平鎖,
reentrankLock 是通過JDK 實現的,既有公平鎖又有非公平鎖

ReentrankLock 優點:
1、既有公平鎖又有非公平鎖
2、提供一個condition類,可分組喚醒需要喚醒的線程
3、 提供能夠中斷等待鎖的線程機制,lock.lockInterruptibly();

synchronize 優點:
1、不用去關心怎樣釋放鎖
2、因爲是jvm實現的, 調試的時候容易找出哪些線程的問題,比如死鎖等問題;

5、ReentrantReadWriteLock讀寫鎖

特徵:
讀鎖和寫鎖不能同時執行,必須等其中一個鎖執行完畢之後才能執行;
缺陷: 當讀操作特別多的時候,寫鎖會一直等待

public class ReentrantLockDemo {
    private final ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
    private final Lock readLock = reentrantReadWriteLock.readLock();
    private final Lock writeLock = reentrantReadWriteLock.writeLock();
    Map<String,Object> map = new HashMap<>();
    public Object setValue(){
        readLock.lock();
        try {
            return "aa";
        }finally {
            readLock.unlock();
        }
    }

    public void setValue(Object a){
        writeLock.lock();
        try {
            map.put("key",a);
        }finally {
            writeLock.unlock();
        }
    }
}
6、StampedLock

J.U.C. 併發組件

1、Future 返回參數的線程

@Log4j2
public class FutureDemo {

     static class MyCallable implements Callable<String>{
        @Override
        public String call() throws Exception {
            log.info("正在加載子線程數據");
            Thread.sleep(5000);
            return "OK";
        }
    }
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Future<String> future = executorService.submit(new MyCallable());
        log.info("加載主線程");
        String s = future.get(); // 當子線程在這一步沒有執行完畢是,需要等待
        log.info("最後的結果:{}",s);
        executorService.shutdown();
        log.info("結束");
    }
}

2、FutureTask

演示源碼:
@Log4j2
@RunWith(SpringRunner.class)
public class FutureTaskDemo {
    @Test
    public void show() throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(()->{
            log.info("正在加載子線程數據");
            Thread.sleep(5000);
            return "OK";
        });
        new Thread(futureTask).start();
        Thread.sleep(1000);
        log.info("加載主線程");
        String s = futureTask.get(); // 當子線程在這一步沒有執行完畢是,需要等待
        log.info("最後的結果:{}",s);
        log.info("結束");
    }

}

併發最佳實現

推薦使用方式

  • 寧可同步,也不要使用wait和notify
  • 使用BlockingQueue實現生產-消費模式
  • 使用併發集合而不是加了鎖的同步集合
  • 使用semaphore創建有界的資源
  • 寧可使用同步代碼快也不使用同步方法
  • 避免使用靜態變量

線程池

1、線程池參數:
corePoolSize : 核心線程數量
maximumPoolSize : 線程最大線程數
workQueue : 阻塞對列,存儲等待執行的任務,很重要,會對線程池運行過程產生重大影響

2、poolSize、corePoolSize、maximumPoolSize三者的關係是如何的呢?
當新提交一個任務時:
(1)如果poolSize<corePoolSize,新增加一個線程處理新的任務。
(2)如果poolSize=corePoolSize,新任務會被放入阻塞隊列等待。
(3)如果阻塞隊列的容量達到上限,且這時poolSize<maximumPoolSize,新增線程來處理任務。
(4)如果阻塞隊列滿了,且poolSize=maximumPoolSize,那麼線程池已經達到極限,會根據飽和策略RejectedExecutionHandler拒絕新的任務。

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