前言:此篇文章是為了寫出一些我對多執行緒的理解,可能沒有涉及到太多的AQS等底層實作原理,但是如果花時間來閱讀的話,也會有很大的識訓的!
多執行緒
行程和執行緒
行程:一個程式
一個行程可以包含多個執行緒,至少包含一個
執行緒:是一個單獨的資源類
Java默認有幾種執行緒
默認是兩個,一個是GC垃圾回收和main方法執行緒
執行緒的幾種狀態
·新建(NEW)
執行緒物件一旦創建就會進入到新生狀態
·就緒(Runnable)
當呼叫start()方法,執行緒立即進入就緒狀態,但不意味著立刻調度執行
·運行(Running)
進入運行狀態,執行緒會自動呼叫run方法,進行一個運行狀態.
·阻塞(Blocked)
當呼叫sleep,wait或同步鎖定時,執行緒進入阻塞狀態,就是代碼不往下執行,阻塞事件解除后,重新進入就緒狀態,等待cpu調度執行
·死亡(DEAO)
執行緒中斷或者結束,一旦進入死亡狀態,就不能再次啟動
wait/sleep區別
1.不同的類 wait是object類 sleep是Thread類
2.鎖的釋放
wait會釋放鎖,sleep不會釋放
3.使用的范圍
wait必須在同步代碼塊中使用(得有一個人等)
sleep可以在任何地方
什么是守護執行緒
執行緒分為用戶執行緒和守護執行緒,守護執行緒為用戶執行緒提供公共服務,在沒有用戶執行緒可服務就會自動離開.
守護執行緒的優先級
優先級比較低,用于為系統中的物件和執行緒提供服務
如何設定守護執行緒
通過SetDaemon(true)設定為”守護執行緒”
生命周期
于執行緒同生共死,當守護的執行緒死亡,守護執行緒也就死亡
虛擬機必須確保用戶執行緒執行完畢:列如 main主執行緒
虛擬機不用等待守護執行緒執行完畢 :列如 GC執行緒
執行緒同步
什么是synchronized?
多個執行緒同時訪問同一個資料,帶來方便的同時,也帶來了訪問沖突問題,我們要保證執行緒同步互斥,也就是指并發執行的多個執行緒,變成在同一時間內只允許一個執行緒訪問共享資源,在訪問時加入同步鎖synchronized,當一個執行緒獲得鎖,獨占資源,其他執行緒必須等待,使用后釋放鎖.
存在問題
1.一個執行緒持有鎖會導致其他所有需要此鎖的執行緒掛起,
2.在多執行緒競爭下,加鎖,釋放鎖會導致比較多的背景關系切換和調度延時,引起性能問題,
3.如果一個優先級高得執行緒等待一個優先級低得執行緒釋放鎖,會導致優先級倒置,引發性能問題
synchronized同步方法
public synchronized void caoyuzheng(){
//同步方法
}
Synchronized塊
synchronized(obj){}//同步代碼塊
我們這里插入一個Synchronized實作賣票的一個例子
package com.cao.demo1;
/**
* @Author 癔癥
* @Date 2020/8/18 10:52
* @Version 1.0
*/
/**
*基本的賣票例子
* 真正的多執行緒開發,公司中的開發,降低耦合性
* 執行緒就是一個單獨的資源類,沒有任何附屬的操作!
*/
public class SynchronizedDemo {
public static synchronized void main(String[] args) {
Ticket ticket = new Ticket();
//函式式介面 @FunctionalInterface jdk1.8 lambad運算式 (引數)->{代碼} 減少了大量的代碼,最大的有點是解決了程式之間的耦合性
new Thread(()->{
for(int i=0;i<40;i++){
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for(int i=0;i<40;i++){
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for(int i=0;i< 40;i++){
ticket.sale();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
}
}
class Ticket{
//屬性,方法
private int num=50;
//賣票的方式
public synchronized void sale(){
if(num>0){
System.out.println(Thread.currentThread().getName()+"賣出了"+(num--)+"票"+"剩余票數"+num);
}
}
}
Synchronized在1.6版本之前一直都是處于一個重量級鎖,但是它在1.6版本之后做了很大的更改,也就是所謂的鎖升級!
鎖升級
無鎖狀態,到它的偏向鎖,再到它的一個輕量級鎖,最后到重量級鎖
偏向鎖
一般在偏向鎖的情況下,它就偏向于獲得第一個鎖的執行緒,它會將執行緒拉到這個鎖物件的物件頭當中,當其他執行緒來的時候,它可能就會立刻結束這個偏向狀態,進而跑到一個輕量級鎖,
輕量級鎖
在低并發情況下來消除鎖的源于,它主要是在虛擬堆疊中開辟一個空間叫Lock Record,將鎖物件的Make word 寫入,再嘗試將另一個Lock Record的指標,使用CAS去修改鎖物件頭的那個區域,完成一個加鎖程序,它也是普遍應用于一個低并發的情況,再往上如果鎖競爭非常激烈,那就會立刻升級為一個重量級鎖.
重量級鎖
用的是一個互斥鎖的程序,通過物件內部的監視器(monitor)實作,而其中 monitor 的本質是依賴于底層作業系統的 Mutex Lock 實作,作業系統實作執行緒之間的切換需要從用戶態切換到內核態,切換成本非常高.重量級鎖的話,同步方法和同步代碼塊不一樣的!
同步代碼塊(重量級鎖)
在編譯之后,會在你的代碼前后加上兩個指令,一個是mointerenter,一個是mointerexit,一個執行緒來的時候,它發現它的鎖標志位是無鎖,是01狀態,它會嘗試給一個互斥鎖物件,鎖物件的時候會跟另一個物件關聯,就是監視器monitor,它會在monitor的一個鎖定器加1.并且將這個monitor的指標寫入到一個物件頭中表示,并且修改它的鎖物件標志位為1 0,就是它重量級鎖的一個標志位,以此完成換鎖的程序,并且它在這個程序是可重入的,因為它不會每次出去之后,再進來需要加鎖和釋放鎖,它每次進來后獲取這個鎖,讓鎖記錄加1即可,它加鎖完之后,當其他執行緒來的時候,它會檢查到這個鎖物件頭中,monitor監視器鎖上計數器不為0,它會在monitor監視狀態下等待去競爭這個鎖,如果之前的操作結束,它就退出開始釋放這鎖,并且逐步的將加上的鎖定釋放幾次,將計數器清零來完成對鎖的一個釋放.讓其他執行緒繼續去競爭這個鎖,這是它重量級鎖同步代碼塊的一個原理.
同步方法(重量級鎖)
同步方法的話,它就不是這種指令了,而是ACC_SYNCHRONIZED標志位,相當于一個flag,當JVM去檢測到這樣一個flag,它自動去走了一個同步方法呼叫的策略,這個原理是比較簡單的.
鎖升級一般不會降級
簡單來說就是鎖會由它鎖競爭的強度而升級.
鎖降級基本上就是進入gc的時候了,所以基本不考慮鎖降級.

Lock鎖
ReentrantLock類實作了Lock,它擁有與synchronized相同的并發性和記憶體語意,在實作執行緒安全的控制中,比較常用的是ReentrantLock,可以顯示加鎖、釋放鎖



ReentrantLock默認使用非公平鎖
默認是使用的非公平鎖,為什么,因為是為了保證公平的,為什么保證公平,假如我前面有個執行緒執行的時間會很花費很長時間,但是后面的執行緒花費及其少的時間,它就會插隊,保證公平,我們可以在里面填入引數true改編成公平鎖
Lock鎖同步代碼的實作
package com.cao.demo1;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author 癔癥
* @Date 2020/8/18 13:26
* @Version 1.0
*/
public class LockDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for(int i=0;i<40;i++) { ticket.sale();
}
},"A").start();
new Thread(()->{
for(int i=0;i<40;i++) { ticket.sale();
}
},"B").start();
new Thread(()->{
for(int i=0;i<40;i++) { ticket.sale();
}
},"C").start();
}
//Lock鎖三部曲
//1.new ReentrantLock();
//2.加鎖, 使用.lock();
//3.解鎖, finally-->.unlock();
class Ticket2{
//屬性,方法
private int num=50;
Lock lock=new ReentrantLock();
public void sale(){
//加鎖
lock.lock();
try {
//業務代碼
if(num>0){
System.out.println(Thread.currentThread().getName()+"賣出了"+(num--)+"票"+"剩余票數"+num);
lock.tryLock();//查看鎖的狀態
}
}catch (Exception e){
}finally {
//解鎖
lock.unlock();
}
}
}
}
公平鎖/非公平鎖
公平鎖:十分共平,保證執行緒之間可以條條有序
舉例來說如果我前面一條執行緒執行需要30秒,而另一條執行緒執行需要3秒,但是3秒的還是會要等待30秒的執行完.
非公平鎖:十分不公平,執行緒之間可以進行一個插隊操作!
舉例來說如果我前面一條執行緒執行需要30秒,而另一條執行緒執行需要3秒,但是3秒的執行緒會提前執行,30秒的后執行.
synchronized和lock的區別
1.Synchronized 無法判斷獲取鎖的狀態,lock可以獲取鎖的狀態

2.Synchronized 會自動釋放鎖,lock必須手動釋放鎖!如果不釋放,會出現死鎖.
3.Synchronized 執行緒需要釋放鎖才能進行執行下一個執行緒,假如出現阻塞會一直等待,lock鎖不一定會等待下去,因為它可以獲取鎖的狀態
4.Synchronized 可重入鎖,不可以中斷的,非公平;lock鎖可重入鎖,可以判斷鎖,非公平(可自動進行設定)
ReentrantLock和Synchronized 的區別
1.synchronized是JVM的一個關鍵字,ReentrantLock其實就是一個類,你需要去手動去編碼.
2.synchronized在使用的時候比較簡單,直接同步代碼塊或者直接同步方法,不需要關心鎖的釋放,但是ReentrantLock需要手動的去lock然后配合try finally代碼塊一定要去把它的鎖給釋放
3.ReentrantLock相比synchronized有幾個高級特性,它提供了一個,如果一個執行緒長期等待不到一個鎖的時候,為了防止死鎖,可以去手動呼叫lockInterruptibly方法,嘗試去釋放這個鎖,釋放自己的資源不去等待
4.ReentrantLock提供了一個,可以構造公平鎖的一個方式,因為它的建構式有一個但是不推薦使用,因為它會讓ReentrantLock等級下降,它提供了一個condition,可以指定去喚醒系結到condition身上的執行緒,來實作選擇性通知的一個機制.
關于選擇性,如果不需要ReentrantLock的特性的話,還是使用synchronized,因為相比來說synchronized的話,它是JVM層面的關鍵字,當優化JDK的時候它會非常方便的去了解,當前的鎖被那些執行緒所持有,這個狀態的話不是ReentrantLock能相比的,還是synchronized比較好些
死鎖
某一個同步塊同時擁有兩個以上物件的鎖時,就可能發生死鎖問題.
產生死鎖的四個必要條件
\1. 互斥條件:一個資源每次只能被一個行程使用,
\2. 請求與保持條件:一個行程因請求資源而阻塞時,對已獲得的資源保持不放.
\3. 不剝奪條件:行程已獲得的資源,在未使用完之前,不能強行剝奪.
\4. 回圈等待條件:若干行程之間形成一種頭尾相接的回圈等待資源關系.
死鎖避免
破任意一個或多個條件就可以避免死鎖,同步中盡量不要嵌套同步
死鎖排查
1.我們可以使用jps -l來查看所有執行緒號
2.然后使用jstack查看所有執行緒號
如果報出例外,說明此執行緒出現了死鎖,這種方法主要是通過日志和堆疊資訊來確定的.
當然也可以使用工具來排查,這里就省略不說了.
并發保證集合安全
CopyOnWriteArrayList
CopyOnWriteArrayList解決并發ArrayList不安全問題
并發下ArrayList是不安全的
解決方案:
1、使用Vector
2、Collections.synchronizedList轉換安全 轉換的synchronized
3、使用 CopyOnWriteArrayList
CopyOnWriteArrayList寫入時復制,計算機程式設計領域的一種優化策略
多個執行緒呼叫的時候,list 讀取的時候,固定的 寫入(覆寫).
在寫入的時候避免覆寫,造成資料問題
讀寫分離
CopyOnWriteArrayList 跟Vector 的區別
CopyOnWriteArrayList 比Vector 效率要高,因為CopyOnWriteArrayList 在Add的時候底層沒有使用同步鎖來實作,Vector在Add的時候底層使用了使同步鎖來實作,使用同步鎖實作效率會比較低.
CopyOnWriteArraySet
并發下Set是不安全的
解決方案
1、Collections.synchronizedList轉換安全 轉換的synchronized
2、使用CopyOnWriteArraySet
補充知識:HashSet底層實作原理
//HashSet底層是使用HashMap實作的
public HashSet() {
map = new HashMap<>();
}
//添加時就單純一個put key是無法重復的
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
//PRESENT就是一個常量固定值
private static final Object PRESENT = new Object();
并發下hashMap是不安全的
1.使用ConcurrentHashMap代替來保證一個執行緒安全
2.放進Collection.Synchronized中來保證執行緒安全.
CountDownLatch
它適合一個執行緒等待一批執行緒達到一個同步點,之前進去就行
它的計數器是不能重用的
減法計數器,
package aadd;
import java.util.concurrent.CountDownLatch;
/**
* @Author 癔癥
* @Date 2020/8/26 20:47
* @Version 1.0
*/
//計數器
public class CoutDownlatchDemo {
public static void main(String[] args) throws InterruptedException {
//總數是6 ,必須要執行任務的時候使用
CountDownLatch downlatch = new CountDownLatch(6);
for(int i=1;i<=6;i++){
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"Go out");
downlatch.countDown();//數量-1
},String.valueOf(i)).start();
}
downlatch.await();//等待計數器歸零,然后向下執行
System.out.println("關閉");
}
}
原理:
countDown(); 數量-1
await(); 等待計數器歸零
CyclicBarrier
它是一批執行緒同時到達一個臨界點,之后再往下走
它的計數器是可以留下來的
加法計數器
package aadd;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @Author 癔癥
* @Date 2020/8/26 21:00
* @Version 1.0
*/
public class CyclicBarrierDemo{
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召喚神龍");
});
for(int i=1;i<=7;i++){
final int temp =i;
new Thread(()->{
System.out.println(
Thread.currentThread().getName()+"收集"+temp+"個龍珠"
);
try {
cyclicBarrier.await();//等待
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore
它就是我們經常稱謂的信號量,它可以維持一組許可證
package aadd;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
/**
* @Author 癔癥
* @Date 2020/8/26 21:12
* @Version 1.0
*/
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);
for (int i = 0; i <=6 ; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"搶到車位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"離開車位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();
}
},String.valueOf(i)).start();
}
}
}
原理:
acquire();//獲取,假設如果已經滿了,等待,等待被釋放為止!
release(); //釋放,會將當前的信號量釋放+1.然后喚醒等待的執行緒!
作用:多個共享資源互斥的作用!并發限流,控制最大的執行緒數!
讀寫鎖
獨享鎖(寫鎖):一次只能被一個執行緒占有
共享鎖(讀鎖):多個執行緒可以同時占有
ReadwriteLock
寫入的時候,只希望同一個執行緒去寫
writeLock.lock方法
讀取的時候所有人都可以讀!
readLock.lock方法
這個時候其實我們已經不知不覺看到了很多鎖了.其實鎖并沒有太難!!!
接下來我們再來聊聊佇列,為執行緒池打一個鋪墊!!!

阻塞佇列
BlockingQueue
寫入:如果佇列滿了,就必須阻塞等待
取:如果是佇列是空的,必須阻塞等待生產.

什么情況下會使用阻塞佇列:多執行緒并發處理,執行緒池!
| 方式 | 拋出例外 | 有回傳值 | 阻塞等待 | 超時等待 |
|---|---|---|---|---|
| 添加 | add | offer | put | offer |
| 移除 | remove | poll | take | poll |
| 判斷佇列首 | element | peek | - | - |
//第一種拋出例外
package bq;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @Author 癔癥
* @Date 2020/8/27 15:45
* @Version 1.0
*/
public class Test {
public static void main(String[] args) {
Test1();
}
public static void Test1(){
//佇列的大小
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(3);
System.out.println( queue.add("a"));
System.out.println( queue.add("b"));
System.out.println( queue.add("c"));
//java.lang.IllegalStateException: Queue full
//System.out.println( queue.add("d"));
//java.util.NoSuchElementException
//System.out.println(queue.remove());
System.out.println(queue.remove());
System.out.println(queue.remove());
System.out.println(queue.remove());
}
}
//第二種不拋出例外
package bq;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @Author 癔癥
* @Date 2020/8/27 15:45
* @Version 1.0
*/
public class Test {
public static void main(String[] args) {
Test2();
}
public static void Test2(){
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(3);
System.out.println(queue.offer("a"));
System.out.println(queue.offer("b"));
System.out.println(queue.offer("c"));
//超出長度,不拋出例外
//System.out.println(queue.offer("d"));
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
//移除超出長度,回傳null
//System.out.println(queue.poll());
}
}
//第三種阻塞等待
package bq;
import java.util.concurrent.ArrayBlockingQueue;
/**
* @Author 癔癥
* @Date 2020/8/27 15:45
* @Version 1.0
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
Test3();
}
public static void Test3() throws InterruptedException {
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(3);
queue.put("a");
queue.put("b");
queue.put("c");
//佇列沒有位置了,一直阻塞
// queue.put("d");
System.out.println( queue.take());
System.out.println( queue.take());
System.out.println( queue.take());
//一直等待 死掉了
System.out.println( queue.take());
}
}
//超時等待
package bq;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* @Author 癔癥
* @Date 2020/8/27 15:45
* @Version 1.0
*/
public class Test {
public static void main(String[] args) throws InterruptedException {
Test4();
}
public static void Test4() throws InterruptedException {
ArrayBlockingQueue queue = new ArrayBlockingQueue<>(3);
queue.offer("a");
queue.offer("b");
queue.offer("c");
//超時退出
// queue.offer("d", 2,TimeUnit.SECONDS);
System.out.println(queue.poll());
System.out.println(queue.poll());
System.out.println(queue.poll());
//超過兩秒就退出
// queue.poll( 2,TimeUnit.SECONDS);
}
}
同步佇列
synchronousQueue
它沒有容量,進去一個元素,必須等待取出來之后,才能再放元素.
package bq;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
/**
* @Author 癔癥
* @Date 2020/8/27 16:25
* @Version 1.0
*/
//同步佇列
//put一個元素,必須先toke取出,否則put不進去
public class synchronousQueueTest {
public static void main(String[] args) {
//同步佇列
BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();
new Thread(()->{
try {
synchronousQueue.put("1");
System.out.println(Thread.currentThread().getName()+"p1");
TimeUnit.SECONDS.sleep(3);
synchronousQueue.put("2");
System.out.println(Thread.currentThread().getName()+"p2");
TimeUnit.SECONDS.sleep(3);
synchronousQueue.put("3");
System.out.println(Thread.currentThread().getName()+"p3");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T1").start();
new Thread(()->{
try {
synchronousQueue.take();
System.out.println(Thread.currentThread().getName()+"------put1");
TimeUnit.SECONDS.sleep(3);
synchronousQueue.take();
System.out.println(Thread.currentThread().getName()+"------put2");
TimeUnit.SECONDS.sleep(3);
synchronousQueue.take();
System.out.println(Thread.currentThread().getName()+"------put3");
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"T2").start();
}
}
執行緒池
執行緒池:三大方法、7大引數、4種拒絕策略
好處:
1、降低資源的消耗
2、提高回應的速度
3、方便管理
三大方法
package pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author 癔癥
* @Date 2020/8/27 17:04
* @Version 1.0
*/
//Executors 工具類 、3大方法
public class Demo1 {
public static void main(String[] args) {
//ExecutorService threadpool = Executors.newSingleThreadExecutor();//單個執行緒
// ExecutorService threadpool2 = Executors.newFixedThreadPool(5);//創建一個固定大小的執行緒池
ExecutorService threadpool3 = Executors.newCachedThreadPool();//創建一個快取執行緒池,遇強則強,遇弱則弱
/* for (int i = 0; i <10 ; i++) {
//使用了執行緒池之后要使用執行緒池來創建執行緒
threadpool.execute(()->{
System.out.println(Thread.currentThread().getName()+"單執行緒執行緒池");
});
}
try {
}catch (Exception e){
}finally {
//執行緒池使用完,執行緒結束,關閉執行緒池
threadpool.shutdownNow();
}*/
for (int i = 0; i <10 ; i++) {
//使用了執行緒池之后要使用執行緒池來創建執行緒
threadpool3.execute(()->{
System.out.println(Thread.currentThread().getName()+"固定大小執行緒池");
});
}
try {
}catch (Exception e){
}finally {
//執行緒池使用完,執行緒結束,關閉執行緒池
threadpool3.shutdownNow();
}
}
}
七大引數
這里我們通過原始碼來進行分析
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
它們都是通過ThreadPoolExecutor來實作的
public ThreadPoolExecutor(int corePoolSize,//核心執行緒大小
int maximumPoolSize, //最大核心執行緒池大小
long keepAliveTime, //超時了沒有人呼叫就會釋放
TimeUnit unit, //超時單位
BlockingQueue<Runnable> workQueue, //阻塞佇列
ThreadFactory threadFactory, //創建執行緒,一般不動
RejectedExecutionHandler handler ) { //拒絕策略
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
四種拒絕策略
AbortPolicy就是拋出例外
CallerRunsPolicy就是 那個執行緒來的,去哪里 就比方main執行緒
DiscardOldestPolicy就是 佇列滿了,會嘗試和最早的競爭,也不會拋出例外
DiscardPolicy就是 不會拋出例外,佇列滿了就停止了
我們去自定義一個執行緒來使用
package pool;
import java.util.concurrent.*;
/**
* @Author 癔癥
* @Date 2020/8/27 17:04
* @Version 1.0
*/
//Executors 工具類 、3大方法
public class Demo1 {
public static void main(String[] args) {
//自定義執行緒池! ThreadPoolExecutor 最常用的執行緒池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()); //如果佇列滿了,還有人進來,不處理,并拋出例外
/* for (int i = 0; i <10 ; i++) {
//使用了執行緒池之后要使用執行緒池來創建執行緒
threadpool.execute(()->{
System.out.println(Thread.currentThread().getName()+"單執行緒執行緒池");
});
}
try {
}catch (Exception e){
}finally {
//執行緒池使用完,執行緒結束,關閉執行緒池
threadpool.shutdownNow();
}*/
for (int i = 0; i<6 ; i++) {
//使用了執行緒池之后要使用執行緒池來創建執行緒
poolExecutor.execute(()->{
System.out.println(Thread.currentThread().getName()+"自定義執行緒");
});
}
try {
//最大承載:阻塞佇列加最大執行緒數 Deque+max 超出此范圍 就會走拒絕策略
}catch (Exception e){
}finally {
//執行緒池使用完,執行緒結束,關閉執行緒池
poolExecutor.shutdownNow();
}
}
}
池的最大的大小如何去設定!
查詢CPU最大核數
Cpu密集型,幾核,就是幾,可以保持CPU的效率最高
IO密集型 >判斷你程式種十分耗IO的執行緒
Runtime.getRuntime().availableProcessors();
volatile
volatile是java虛擬機提供輕量級的同步機制
1、保證可見性
package ACID;
import java.util.concurrent.TimeUnit;
/**
* @Author 癔癥
* @Date 2020/8/30 11:03
* @Version 1.0
*/
// 作業記憶體感知不到主記憶體發生了變化,所以程式才不會結束
public class Vdemo02 {
private static int num=0;
public static void main(String[] args) {
new Thread(()->{
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}

不加volatile會出現死回圈,因為執行緒感知不到外邊值的一個變化,加了volatile會保證一個可見性
package ACID;
import java.util.concurrent.TimeUnit;
/**
* @Author 癔癥
* @Date 2020/8/30 11:03
* @Version 1.0
*/
// 作業記憶體感知不到主記憶體的變化,所以程式才不會結束
public class Vdemo02 {
//加上volatile,作業記憶體可以感知到主記憶體的變化,保證一致性
private volatile static int num=0;
public static void main(String[] args) {
new Thread(()->{//對主記憶體的變化是不知道的
while (num==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num=1;
System.out.println(num);
}
}
這里的可見性采用的是一個嗅徑訓制和MESI機制.
總線風暴
不斷的總線嗅探機制會出現問題
由于volatile的mesi快取一致性協議需要不斷的從主記憶體嗅探和cas不斷回圈無效互動導致總線帶寬達到峰值
解決方案: 部分volatile和cas使用synchronize
2、不保證原子性
原子性:不可分割,要么同時成功,要么同時失敗
執行緒A在執行任務的時候,不能被打擾的,也不能被分割,
package ACID;
/**
* @Author 癔癥
* @Date 2020/8/30 19:35
* @Version 1.0
*
*/
//不保證原子性
public class VDemo03 {
public static int num;
public static void add(){
num++;
};
public static void main(String[] args) {
//num應該是20000,但是它可能輸出的不是兩萬,為什么?
for (int i = 1; i <=20;i++) {
new Thread(()->{
for (int j = 0; j <1000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){// main gc
Thread.yield();//暫停執行緒
}
System.out.println(Thread.currentThread().getName()+"------------"+num);
}
}
原子類
Atomic就是原子類
加上synchronized就可以保證一個原子性,因為synchronized默認是支持原子性的
但是加上volatile不會保證原子性
使用原子類 解決原子性問題,我們根本你不需要使用volatile和synchronized
package ACID;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author 癔癥
* @Date 2020/8/30 19:35
* @Version 1.0
*
*/
//不保證原子性
public class VDemo03 {
//不保證原子性
//我們可以使用原子類
public static AtomicInteger atomicInteger =new AtomicInteger();
public static void add(){
//不是一個原子性操作
//num++;
atomicInteger.getAndIncrement();//使用原子類執行+1的操作
};
public static void main(String[] args) {
//num應該是20000,但是它可能輸出的不是兩萬,為什么?
for (int i = 1; i <=20;i++) {
new Thread(()->{
for (int j = 0; j <1000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){// main gc
Thread.yield();//暫停執行緒
}
System.out.println(Thread.currentThread().getName()+"------------"+atomicInteger);
}
}
原子類的底層直接和作業系統掛鉤,在記憶體中修改值!它底層是一個Unsafe類,Unsafe底層大量呼叫了native方法,說明呼叫的是其他語言的方法.(很深層).
3、禁止指令重排
代碼在編譯的時候,指令會進行一個重新排序,這個就是指令重排
加了volatile關鍵字會產生記憶體屏障 作用:
1、保證特定的操作的執行順序!
2、可以保證某些變數的記憶體可見性(利用這些特性volatile實作了可見性)
讀寫的時候,加上volatile會在上下產生一個記憶體屏障,去禁止上下指令順序交換

總結:volatile保證可見性,不保證原子性,由于記憶體屏障,可以保證避免指令重排的現象產生
CAS
CAS(比較并交換),在并發不是特別大的情況下,鎖競爭不激烈,你要去修改這個東西,你要先查,查完之后,再修改,修改完,準備寫進去之前,它會再查一次,比較之前的結果有沒有區別,如果有區別說明這個修改是不安全的.如果要是沒有區別,說明這個修改是安全的,這個時候它就可以安全的去修改,而不是直接加鎖的那種形式,在低并發的情況性能會好一點!
缺點:
1、由于底層是自旋鎖,回圈會耗時
2、一次只能保證一個共享變數的原子性
3、高并發情況下大量使用它會出現ABA問題
ABA問題
package cas;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @Author 癔癥
* @Date 2020/8/31 19:04
* @Version 1.0
*/
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
//如果我們期望的值達到了,那么就更新,否則,就不更新,CAS是 CPU的并發原語
System.out.println(atomicInteger.compareAndSet(2020,2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021,2020));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2020,6666));
System.out.println(atomicInteger.get());
}
}
之前讀和再過讀中間可能被第三人修改過,但是又給改了回來.
原子參考解決ABA
AtomicStampedReference增加版本號,如果有人來修改,則增加版本號.
package cas;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @Author 癔癥
* @Date 2020/8/31 19:04
* @Version 1.0
*/
public class CASDemo {
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
public static void main(String[] args) {
new Thread(()->{
int stamp=atomicStampedReference.getStamp();//獲得這個版本號
System.out.println("a----->"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//compareAndSet 比較并交換
System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("aa----->"+atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("aaa----->"+atomicStampedReference.getStamp());
},"A").start();
//底層是使用了樂觀鎖原理
new Thread(()->{
int stamp= atomicStampedReference.getStamp();
System.out.println("b----->"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 6, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("bb----->"+atomicStampedReference.getStamp());
},"B").start();
}
}
思想跟樂觀鎖幾乎一樣
可重入鎖
可重入鎖(遞回鎖):拿到了外面的鎖之后,就可以拿到里面的鎖,自動獲取
自旋鎖
CAS底層就是通過自旋鎖來實作的.
在Java中,自旋鎖是指嘗試獲取鎖的執行緒不會立即阻塞,而是采用回圈的方式去嘗試獲取鎖,這樣的好處是減少執行緒背景關系切換的消耗,缺點是回圈會消耗CPU
總結:謝謝大家的支持,如果文章中有什么錯誤或者那塊理解的不是很到位,請各位及時補充一下!不喜勿噴,互相幫助!共同進步!

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/57534.html
標籤:其他
上一篇:萬字圖解Java多執行緒
