一個簡單的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拒絕新的任務。