1. 程式人生 > 實用技巧 >JUC知識點總結(知識點見內部目錄)

JUC知識點總結(知識點見內部目錄)

目錄

JUC是什麼

JUC就是java.util .concurrent工具包的簡稱。是處理執行緒的工具包,JDK 1.5更新的。

在併發情況下,需要加鎖以防各種併發問題,下面來看下原生的鎖和JUC下的鎖實現方式

Synchronized VS Lock 實現差異


public class Demo1Controller {
    public static void main(String[] args) {
        TicketBySynchronized ticket=new TicketBySynchronized();
        new Thread(()-> {
            for (int i = 0; i < 20; i++) {
                ticket.sall1();
            }
        },"a").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sall1();
            }
        },"b").start();
        new Thread(()->{
            for (int i = 0; i < 20; i++) {
                ticket.sall1();
            }
       },"c").start();
    }
}
/*
 *原版synchronized解決併發程式碼
 */
class TicketBySynchronized{
    private int count=10;//一共有10張票
    public  void sall1(){
        synchronized (this) {//同步程式碼塊方式
            if(count>0){
                    try {
                        TimeUnit.SECONDS.sleep(1);//加等待是為了方便復現不加synchronized併發問題
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(String.format("執行緒%s買到了第%s張票", Thread.currentThread().getName(), count--));
            }
        }
    }
    public synchronized void sall2(){//加到方法上的方式
        if(count>0){
            try {
                TimeUnit.SECONDS.sleep(1);//加等待是為了方便復現不加synchronized併發問題
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(String.format("執行緒%s買到了第%s張票",Thread.currentThread().getName(),count--));
        }
    }
}
/**
 * JUC ReentrantLock防止併發程式碼如下
 */
class TicketByJuc{
    private int count=10;//一共有10張票
    Lock reentrantLock = new ReentrantLock();//可重入鎖
    public  void sall(){
        reentrantLock.lock();
        try {
            if(count>0){
                try {
                    TimeUnit.SECONDS.sleep(1);//加等待是為了方便復現不加synchronized併發問題
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(String.format("執行緒%s買到了第%s張票", Thread.currentThread().getName(), count--));
            }
        }finally {
            reentrantLock.unlock();//必須要在finally中解鎖,以防死鎖
        }

    }
}

Synchronized & Lock 總結

  1. Synchronized是java內建關鍵字,在jvm層面,Lock是個java類;

  2. Synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖;

  3. Synchronized會自動釋放鎖(正常執行完會釋放、發生異常會釋放);

Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成執行緒死鎖;

  1. 用Synchronized關鍵字的兩個執行緒1和執行緒2,如果當前執行緒1獲得鎖,執行緒2執行緒等待。如果執行緒1
    阻塞,執行緒2則會一直等待下去;

Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,執行緒可以不用一直等待就結束了;

  1. Synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷&可公平
  2. Lock鎖適合大量同步的程式碼的同步問題,Synchronized鎖適合程式碼少量的同步問題。

Synchronized鎖的物件是什麼

非靜態方法,鎖的就是呼叫方法的那個例項物件

靜態方法,鎖的是class檔案

同步程式碼塊,鎖的是配置的物件

生產者&消費者

只有兩個執行緒的生產者消費者模式

程式流程,當庫存等於1的時候,A執行緒生產,否則A執行緒等待,當庫存大於1的時候,B執行緒消費,否則B執行緒等待

a. 傳統模式,synchronized

public class Demo1 {
    public static void main(String[] args) {
        PublicData1 pub=new PublicData1();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.produce();//啟動生產者,生產50次
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.consume();//啟動消費者,消費50次
            }
        },"B").start();
    }
}

/**
 * 公共資源抽取(傳統模式,synchronized)
 */
class PublicData1{
    private int count=0;

    public synchronized void produce(){
        if(count!=0){
            //有庫存,生產者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //沒庫存,生產者執行(count++)
        System.out.println(String.format("執行緒%s生產了%s",Thread.currentThread().getName(),count++));
        //喚醒其他執行緒
        this.notifyAll();
    }

    public synchronized void consume(){
        if(count==0){
            //沒庫存,消費者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //有庫存,消費者執行(count--)
        System.out.println(String.format("執行緒%s消費了%s",Thread.currentThread().getName(),count--));
        //喚醒其他執行緒
        this.notifyAll();
    }
}

b. JUC模式

public class Demo1 {
    public static void main(String[] args) {
        PublicData2 pub=new PublicData2();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.produce();//啟動生產者,生產50次
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.consume();//啟動消費者,消費50次
            }
        },"B").start();
    }
}

/**
 * 公共資源抽取(JUC模式)
 */
class PublicData2{
    private int count=0;
    Lock lock=new ReentrantLock();
    Condition condition = lock.newCondition();
    public void produce(){
        lock.lock();
        try {
            if(count!=0){
                //有庫存,生產者等待
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //沒庫存,生產者執行(count++)
            System.out.println(String.format("執行緒%s生產了%s",Thread.currentThread().getName(),count++));
            //喚醒其他執行緒
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
    public void consume(){
        lock.lock();
        try {
            if(count==0){
                //沒庫存,消費者等待
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //有庫存,消費者執行(count--)
            System.out.println(String.format("執行緒%s消費了%s",Thread.currentThread().getName(),count--));
            //喚醒其他執行緒
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}

兩個執行緒以上的生產者消費者模式

a. 傳統模式,synchronized

public class Demo1 {
    public static void main(String[] args) {
        PublicData1 pub=new PublicData1();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.produce();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.consume();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.produce();
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.consume();
            }
        },"D").start();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.produce();
            }
        },"E").start();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.consume();
            }
        },"F").start();
    }
}
/**
 * 公共資源抽取(傳統模式,synchronized)
 */
class PublicData1{
    private int count=0;
    public synchronized void produce(){
        while (count!=0){
            //有庫存,生產者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //沒庫存,生產者執行(count++)
        System.out.println(String.format("執行緒%s生產了%s",Thread.currentThread().getName(),count++));
        //喚醒其他執行緒
        this.notifyAll();
    }

    public synchronized void consume(){
        while(count==0){
            //沒庫存,消費者等待
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //有庫存,消費者執行(count--)
        System.out.println(String.format("執行緒%s消費了%s",Thread.currentThread().getName(),count--));
        //喚醒其他執行緒
        this.notifyAll();
    }
}

b. JUC模式

public class Demo1 {
    public static void main(String[] args) {
        PublicData2 pub=new PublicData2();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.produce();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.consume();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.produce();
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.consume();
            }
        },"D").start();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.produce();
            }
        },"E").start();
        new Thread(()->{
            for (int i = 0; i <50 ; i++) {
                pub.consume();
            }
        },"F").start();
    }
}

/**
 * 公共資源抽取(JUC模式)
 */
class PublicData2{
    private int count=0;
    Lock lock=new ReentrantLock();
    Condition condition = lock.newCondition();
    public void produce(){
        lock.lock();
        try {
            while (count!=0){
                //有庫存,生產者等待
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //沒庫存,生產者執行(count++)
            System.out.println(String.format("執行緒%s生產了%s",Thread.currentThread().getName(),count++));
            //喚醒其他執行緒
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
    public void consume(){
        lock.lock();
        try {
            while(count==0){
                //沒庫存,消費者等待
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            //有庫存,消費者執行(count--)
            System.out.println(String.format("執行緒%s消費了%s",Thread.currentThread().getName(),count--));
            //喚醒其他執行緒
            condition.signalAll();
        }finally {
            lock.unlock();
        }
    }
}

兩個執行緒 & 兩個以上執行緒的消費者生產者區別對比

有多個執行緒(兩個以上)同時對公共資源進行操作(多個加、減操作),會導致虛假喚醒的情況出現,程式碼體現如下

精準喚醒

上面的例子,雖然保證了生產和消費是有序的,但無法保證多個執行緒如何精準喚醒,既ABCD四個執行緒執行是亂序的,下面來看如何指定執行緒喚醒

實現A\B\C三個執行緒順序輸出abcabcabc

public class Demo2 {
    public static void main(String[] args) {
        PublicData pub=new PublicData();
        new Thread(()->{
            for (int i = 0; i <3 ; i++) {
                pub.A();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i <3 ; i++) {
                pub.B();
            }
        },"B").start();
        new Thread(()->{
            for (int i = 0; i <3 ; i++) {
                pub.C();
            }
        },"C").start();
    }
}

/**
 * 公共資源抽取()
 */
class PublicData{
    private int tag=0;// 0=A執行緒 1=B執行緒 2=C執行緒
    Lock lock=new ReentrantLock();
    Condition conditionA = lock.newCondition();
    Condition conditionB = lock.newCondition();
    Condition conditionC = lock.newCondition();
    public void A(){
        lock.lock();
        try {
            //判斷A執行緒是否可執行
            while (tag !=0){
                //不為0,則等待
                try {
                    conditionA.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            tag=1;//將標記為修改為下一個執行緒B的
            System.out.println(String.format("執行緒%s執行%s",Thread.currentThread().getName(),"a"));
            conditionB.signal();//喚醒B執行緒
        }finally {
            lock.unlock();
        }
    }
    public void B(){
        lock.lock();
        try {
            //判斷B執行緒是否可執行
            while (tag !=1){
                //不為1,則等待
                try {
                    conditionB.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            tag=2;//將標記為修改為下一個執行緒C的
            System.out.println(String.format("執行緒%s執行%s",Thread.currentThread().getName(),"b"));
            conditionC.signal();//喚醒C執行緒
        }finally {
            lock.unlock();
        }

    }
    public void C(){
        lock.lock();
        try {
            //判斷B執行緒是否可執行
            while (tag !=2){
                //不為2,則等待
                try {
                    conditionC.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            tag=0;//將標記為修改為下一個執行緒A的
            System.out.println(String.format("執行緒%s執行%s",Thread.currentThread().getName(),"c"));
            conditionA.signal();//喚醒A執行緒
        }finally {
            lock.unlock();
        }

    }
}

多執行緒下集合如何保證併發安全

注:java中所有的集合都是不安全的,併發下會報 ConcurrentModificationException異常

CopyOnWriteArray

List

JUC的方式:CopyOnWriteArrayList

public static void main(String[] args) {
        CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList();//獲得一個執行緒安全的List,底層採用複製寫入方法
        for (int i = 0; i <10 ; i++) {
            int finalI = i;
            new Thread(()->{
                copyOnWriteArrayList.add(finalI);
                System.out.println(copyOnWriteArrayList);
            }).start();
        }
    }

其他保證安全的解決辦法

  1. Vector是執行緒安全類,在JDK1.0就有了,比ArrayList早,效能差
  2. 使用Collections.synchronizedList()修改為安全的,效能差

為什麼CopyOnWriteArray效能要高

Vector是增刪改查方法都加了synchronized,保證同步,但是每個方法執行的時候都要去獲得鎖,效能就會大大下降,而CopyOnWriteArrayList 只是在增刪改上加鎖,但是讀不加鎖,在讀方面的效能就好於Vector,CopyOnWriteArrayList支援讀多寫少的併發情況

Set

JUC的方式:CopyOnWriteArraySet

    public static void main(String[] args) {
        CopyOnWriteArraySet copyOnWriteArraySet = new CopyOnWriteArraySet();
        for (int i = 0; i <50 ; i++) {
            int finalI = i;
            new Thread(()->{
                copyOnWriteArraySet.add(finalI);
                System.out.println(copyOnWriteArraySet);
            }).start();
        }
    }

注:hashset底層就是hashmap

其他保證安全的解決辦法

Collections.synchronizedSet()

Map

JUC的方式:ConcurrentHashMap

    public static void main(String[] args) {
        ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap();
        for (int i = 0; i <100 ; i++) {
            int finalI = i;
            new Thread(()->{
                concurrentHashMap.put(finalI,finalI);
                System.out.println(concurrentHashMap);
            }).start();
        }
    }

其他保證安全的解決辦法

Collections.synchronizedMap()

Callable

帶返回值的執行緒

public class Demo1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        PublicCallable callable=new PublicCallable();
        FutureTask task=new FutureTask(callable);//用它包裝一下實現Callable的物件
        new Thread(task).start();//Thread類不能直接接收一個Callable類,所以找官方文件,發現Runnable有一個實現類FutureTask
        System.out.println(task.get());//這會有返回值,如果執行緒還未執行完,會阻塞下去
//        System.out.println(task.get(2,TimeUnit.SECONDS));可以設定等待多久,超時會報TimeoutException
    }
}
class PublicCallable implements Callable<String> {
    @Override
    public String call() throws Exception {
        System.out.println("呼叫了");
        TimeUnit.SECONDS.sleep(2);
        return "ok";
    }
}

多執行緒常用輔助類

CountDownLatch

可設定一個屏障,只有指定數量執行緒|功能執行完後(--操作),才去執行指定程式碼的,否則就一直等待

//不論怎麼執行,end都是最後一個出現 
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        CountDownLatch countDownLatch = new CountDownLatch(3);
        for (int i = 0; i <5 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName());
                countDownLatch.countDown();//執行完一個,就減一個
            },"t"+i).start();
        }
        countDownLatch.await();//當CountDownLatch(3) 三個執行後,主程式繼續執行
        //countDownLatch.await(2,TimeUnit.SECONDS);//可設定一個超時時間,如果超時未執行完,則返回false,否則返回true,後繼續輸出end
        System.out.println("end");
    }

CountDownLatch 主要有兩個方法,當一個或多個執行緒呼叫 await 方法時,這些執行緒會阻塞
其他執行緒呼叫CountDown方法會將計數器減1(呼叫CountDown方法的執行緒不會阻塞)
當計數器變為0時,await 方法阻塞的執行緒會被喚醒,繼續執行

應用場景

CyclicBarrier

可設定一個屏障,只有指定數量執行緒|功能執行完後(++操作),才去執行程式碼的,否則就一直等待

public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3,()->{//三個人都到的時候才執行下面的
            System.out.println("人數夠了");
        });
        new Thread(()->{
            System.out.println("第一個人到了");
            try {
                cyclicBarrier.await();//人數++操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            System.out.println("第二個人到了");
            try {
                cyclicBarrier.await();//人數++操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            System.out.println("第三個人到了");
            try {
                cyclicBarrier.await();//人數++操作
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }).start();
    }

應用場景

Semapore

最大訪問量控制,執行緒池、流量控制

public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);//設定只有三個執行緒的位置
        for (int i = 0; i <5; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();//每個執行緒開始執行前,先去獲取一下,是否還有位置,沒有位置就阻塞等待
                    System.out.println(Thread.currentThread().getName()+"-in");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+"-out");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();//執行完釋放位置,其他等待執行緒加入執行
                }
            },"A"+i).start();
        }
    }

應用場景

讀寫鎖

ReenTrantReadWriteLock

關係: 讀讀可並行;讀寫不可並行;寫寫不可並行

下面的例子如果不加讀寫鎖,那麼就不能保證寫的時候**一定是 **開始->執行中->結束

如果加上讀寫鎖,如果線上程A讀的時候發現還有執行緒B在寫同一個資料,則會等待執行緒B寫完後再繼續,防止B剛讀完,A把資料修改了,造成髒讀

public static void main(String[] args) {
        ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        ReentrantReadWriteLock.ReadLock readLock = lock.readLock();//獲得讀鎖
        ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();//獲得寫鎖
        for (int i = 0; i <5 ; i++) {
            new Thread(()->{
                try {
                    writeLock.lock();//寫鎖加鎖
                    exe_write();
                }finally {
                    writeLock.unlock();//寫鎖釋放
                }
            },"寫"+i).start();
        }
        for (int i = 0; i <5 ; i++) {
            new Thread(()->{
                try {
                    readLock.lock();//讀鎖加鎖
                    exe_read();
                }finally {
                    readLock.unlock();//讀鎖釋放
                }
            },"讀"+i).start();
        }
    }
    public static void exe_write(){
        String name = Thread.currentThread().getName();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+"開始");
        System.out.println(name+"執行中");
        System.out.println(name+"結束");
    }
    public static void exe_read(){
        String name = Thread.currentThread().getName();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(name+"開始");
        System.out.println(name+"執行中");
        System.out.println(name+"結束");
    }

對比:

阻塞佇列

只列舉了部分,更多見JDK8文件

當佇列是空的,從佇列中獲取元素的操作將會被阻塞。
當佇列是滿的,從佇列中新增元素的操作將會被阻塞。
試圖從空的佇列中獲取元素的執行緒將會被阻塞,直到其他執行緒往空的佇列插入新的元素。

ArrayBlockingQueue

由陣列組成的佇列,必須指定佇列大小

常用方法

ArrayBlockingQueue queue = new ArrayBlockingQueue(3);//陣列實現的阻塞佇列(必須指定佇列大小)
        queue.add("a1");//向佇列中插入一個元素,如果超出佇列數量,則丟擲Queue full異常
        queue.offer("a2");//嘗試向佇列中插入元素,成功返回true,否則返回false
        queue.contains("a1");//判斷佇列中是否有指定元素,有的話返回true,佇列為空則 拋異常 NoSuchElementException
        queue.element();//獲得隊首的元素,如果佇列為空會拋異常 NoSuchElementException
        queue.remove();//刪除隊首元素,如果佇列為空會拋異常 NoSuchElementException
        queue.take();//獲得隊首元素,如果佇列為空,則阻塞等待到有值為止
        queue.peek();//獲得隊首元素,但不從佇列中移除,如果佇列中沒有元素,返回null
        queue.poll();//獲得隊首元素,並且從佇列中移除,如果佇列中沒有元素,返回null;可設定超時時間,等待指定時間後如果沒有獲取到資料,則返回null   

LinkedBlockingQueue

由連結串列組成的佇列,可不指定佇列大小,預設大小為 Integer.MAX_VALUE

常用方法

參考ArrayBlockingQueue

SynchronousQueue

佇列只有一個位置,不儲存多餘的元素

常用方法

參考ArrayBlockingQueue

LinkedBlockingDeque

由連結串列組成的雙端佇列,可不指定佇列大小,預設大小為 Integer.MAX_VALUE

常用方法

LinkedBlockingDeque deque = new LinkedBlockingDeque(2);
            deque.add("a1");//在隊尾新增元素,如果佇列超出大小,丟擲 Deque full
            deque.addFirst("a2");//在隊首新增元素,如果佇列超出大小,丟擲 Deque full
            deque.addLast("a3");//在隊尾新增元素,如果佇列超出大小,丟擲 Deque full
            deque.element();//獲得隊首元素,不在佇列中刪除,佇列為空丟擲NoSuchElementException
            deque.getFirst();//獲得隊首元素,不在佇列中刪除,佇列為空丟擲NoSuchElementException
            deque.getLast();//獲得隊尾元素,不在佇列中刪除,佇列為空丟擲NoSuchElementException
            deque.offerFirst("a4");//在隊首插入元素,如果成功返回true,否則返回false,如果佇列滿了,可以指定等待時間,如果時間內有空間了,就返回true
            deque.offerLast("a5");//在隊尾插入元素,如果成功返回true,否則返回false,如果佇列滿了,可以指定等待時間,如果時間內有空間了,就返回true
            deque.peekFirst();//獲得隊首元素,但不從佇列中刪除,佇列為空返回null
            deque.peekLast();//獲得隊尾元素,但不從佇列中刪除,佇列為空返回null
            deque.pollFirst();//獲得隊首元素,從佇列中刪除,佇列為空返回null,可以指定等待時間,如果時間內有元素了,就返回
            deque.pollLast();////獲得隊尾元素,從佇列中刪除,佇列為空返回null,可以指定等待時間,如果時間內有元素了,就返回
            deque.remove("a1");//刪除指定元素,成功true 否則false
            deque.removeFirst();//刪除隊首元素,並返回刪除的元素,佇列為空丟擲異常NoSuchElementException
            deque.removeLast();//刪除隊尾元素,並返回刪除的元素,佇列為空丟擲異常NoSuchElementException
            deque.takeFirst();//刪除隊首元素,並返回刪除的元素,佇列為空則阻塞等待
            deque.takeLast();//刪除隊尾元素,並返回刪除的元素,佇列為空則阻塞等待

執行緒池

Executors

newFixedThreadPool

public static void main(String[] args) throws InterruptedException, ExecutionException {
    //建立一個固定長度(核心執行緒和最大執行緒數都為3的執行緒),沒有過期時間,一個大小為Integer.MAX_VALUE的連結串列阻塞佇列
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        for (int i = 0; i <10 ; i++) {//執行10個沒有返回值的執行緒
            fixedThreadPool.execute(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("執行緒"+Thread.currentThread().getName()+"來了");
            });
        }
        for (int i = 0; i < 10; i++) {//執行10個有返回值的執行緒
            FutureTask futureTask = new FutureTask(new FutureTaskDemo());
            fixedThreadPool.submit(futureTask);
            System.out.println(futureTask.get());//獲得執行緒執行後的返回值,如果還沒返回阻塞等待
        }
        fixedThreadPool.shutdown();//關閉執行緒池
    }
    static class FutureTaskDemo implements Callable<String>{
        @Override
        public String call() throws Exception {
            String name = Thread.currentThread().getName();
            System.out.println("執行緒"+ name +"開始休息");
            TimeUnit.SECONDS.sleep(2);
            return "執行緒"+ name +"休息了兩秒,通知上級";
        }
    }

newCachedThreadPool

ExecutorService executorService = Executors.newCachedThreadPool();
//建立一個可變長度的(大小0~Integer.MAX_VALUE),存活時間60秒的佇列
//使用方法同fixedThreadpool

newSingleThreadPool

 ExecutorService executorService = Executors.newSingleThreadExecutor();
//建立一個只有定長的執行緒池(只有一個執行緒),並用LinkedBlockingQueue儲存還未執行的(LinkedBlockingQueue長度為Integer.MAX_VALUE)
//使用方法同fixedThreadpool

newScheduledThreadPool

    public static void main(String[] args) throws Exception {
        //建立一個核心執行緒數為指定個數,最大執行緒數為Intager.MAX_VALUE的執行緒池,建立的執行緒不會自動失效
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
        FutureTask futureTask = new FutureTask(new FutureTaskDemo());
        pool.scheduleWithFixedDelay(()->{
            System.out.println("執行緒"+Thread.currentThread().getName());
        },1,5,TimeUnit.SECONDS);//指定首次執行在1秒以後,然後保持5秒鐘執行一次
    }

newSingleThreadScheduledExecutor

ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
//建立一個建立一個核心執行緒數為1的執行緒池,其他方法同newScheduledThreadPool

注意

以上使用Executors建立執行緒池都有隱患,阿里開發手冊也明確禁止使用,因為上面都用了Intager.MAX_VALUE,會導致各種記憶體溢位等問題。

推薦用ThreadPoolExecutor建立

ThreadPoolExecutor

七個引數

corePollSize :核心執行緒數。在建立了執行緒池後,執行緒中沒有任何執行緒,等到有任務到來時才建立執行緒去執行任務。預設情況下,在建立了執行緒池後,執行緒池中的 執行緒數為0,當有任務來之後,就會建立一個執行緒去執行任務,當執行緒池中的執行緒數目達到corePoolSize後,就會把到達的任務放到快取隊列當中。
maximumPoolSize :最大執行緒數。表明執行緒中最多能夠建立的執行緒數量,此值必須大於等於1。
keepAliveTime :空閒的執行緒保留的時間。
TimeUnit :空閒執行緒的保留時間單位。
BlockingQueue< Runnable> :阻塞佇列,儲存等待執行的任務。引數有ArrayBlockingQueue、LinkedBlockingQueue、SynchronousQueue可選。
ThreadFactory :執行緒工廠,用來建立執行緒,一般預設即可

RejectedExecutionHandler:佇列已滿,而且任務量大於最大執行緒的異常處理策略。

四個拒絕策略

ThreadPoolExecutor.AbortPolicy:丟棄任務並丟擲RejectedExecutionException異常。
ThreadPoolExecutor.DiscardPolicy:也是丟棄任務,但是不丟擲異常。
ThreadPoolExecutor.DiscardOldestPolicy:丟棄佇列最前面的任務,然後重新嘗試執行任務
(重複此過程)
ThreadPoolExecutor.CallerRunsPolicy:由呼叫執行緒處理該任務

原理

函式式介面

Function

函式型介面,有一個輸入,有一個輸出 ,可指定入參出參的型別,Function<T, R>

public static void main(String[] args) {
        Function<Integer, String> function = new Function<Integer, String>() {
            @Override
            public String apply(Integer integer) {
                return "test"+integer;
            }
        };
    //上面是非簡化版的,下面是通過lombda簡化版的
        Function<Integer,String> simple_function=num->{return "test"+num;};//拿到傳入的物件做點事情後返回
        System.out.println(function.apply(2));
    }

Predicate

斷定型介面,有一個輸入引數,返回只有布林值 ,可指定入參型別

    public static void main(String[] args) {
        Predicate<String> stringPredicate = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return false;
            }
        };
        //上面是非簡化版的,下面是通過lombda簡化版的
        Predicate<String> predicate=s -> {return true;};//拿到傳入的物件做點事情後返回
        System.out.println(predicate.test("a"));
    }

Consumer

消費型介面,有一個輸入引數,沒有返回值 可指定入參型別

public static void main(String[] args) {
        Consumer<String> stringConsumer = new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println("處理了資料"+s);
            }
        };
    //上面是非簡化版的,下面是通過lombda簡化版的
        stringConsumer.accept("rb");
        Consumer<String> simple_stringConsumer =s -> { System.out.println("處理了資料"+s);};//拿到傳入的物件做點事情
        simple_stringConsumer.accept("RB");
    }

Supplier

供給型介面,沒有輸入引數,只有返回引數

    public static void main(String[] args) {
        Supplier supplier = new Supplier() {
            @Override
            public Object get() {
                return "rb";
            }
        };
         //上面是非簡化版的,下面是通過lombda簡化版的
        System.out.println(supplier.get());
        Supplier simple_supplier =()->{return "RB";};//返回某些業務中的資料
        System.out.println(simple_supplier.get());
    }

Stream流式計算

測試程式碼

@Data
class User {//vo
    private int id;
    private String userName;
    private int age;
}

List<User> list=new ArrayList();
for (int j = 10; j >=0 ; j--) {//封裝測試資料
    User user = new User();
    int age = j - 1;
    user.setAge(age);
    user.setId(j);
    user.setUserName("RB"+j);
    list.add(user);
}
Stream<User> stream = list.stream();//獲得流

filter

 stream.filter(u -> u.getAge() > 3 && u.getId()>4);//過濾年齡大於3歲並且id大於4的

值求和計算

stream.mapToInt(User::getAge).sum();//對年齡求和 mapToDouble	mapToLong mapToInt

MaxMin,獲得流中最大&最小的元素

stream.max(Comparator.comparing(User::getId).reversed());//獲得集合中最小的元素,不加reversed()就是返回最大的
//stream.min同理

Match,判斷流中是否包含指定關鍵字

當所有元素都 -不具備- 指定關鍵字的時候,返回true

stream.noneMatch(user -> user.getUserName().contains("1"));//user集合中姓名都不包含1,返回true

當所有元素都 -具備- 指定關鍵字的時候,返回true

stream.allMatch(user -> user.getUserName().contains("B"));//user集合中姓名都包含B,返回true

-只要- 有元素具備指定關鍵字的時候,返回true

stream.anyMatch(user -> user.getUserName().contains("B10"));//user集合中姓名有包含B10的,返回true

map

stream.map(user-> user.getUserName().toLowerCase());//將集合中姓名轉大寫,並且將此列返回

findFirst

System.out.println(stream.findFirst());//獲得第一個元素

limit

stream.limit(2);獲得下標0-1的元素

distinct

stream.distinct();//去重

sorted

stream.sorted(Comparator.comparing(User::getId).reversed());//按照id倒序,不加.reversed()就是按照id正序
Arrays.asList(3,4,1,2,5).stream().sorted(((o1, o2) -> o2.compareTo(o1)))//如果集合中不是物件,直接呼叫.sorted()就是正序,否則傳入對應引數為倒序

forEach

stream.forEach(System.out::println);//迴圈輸出處理後的資料

collect

System.out.println(stream.collect(Collectors.toList()));//將結果轉為list集合
System.out.println(stream.collect(Collectors.toSet()));//將結果轉為set集合
System.out.println(stream.collect(Collectors.toMap(user -> user.getId(), user -> user)));//返回一個map,key=userid,value=User物件

ForkJoin

public static void main(String[] args) throws Exception {
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask forkJoinDemo = new ForkJoinDemo(0L, 100000000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinDemo);
        System.out.println(submit.get());
    }
//繼承RecursiveTask是有返回值的,繼承RecursiveAction是無返回值的,無返回值的compute如下
    //@Override
    //protected void compute() {}
class ForkJoinDemo extends RecursiveTask<Long> {
    private Long start;//起始值
    private Long end;//結束值
    public static final Long upper_limit = 1000L;//最小任單位,小於這個值直接計算,否則就進行任務拆分
    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }
    @Override
    protected Long compute() {
        long len = end - start;
        if(len<=upper_limit){//判斷是否是最小任務單位
            //直接執行後返回
            long count=0L;
            for (long i = start; i < end; i++) {
                count=i+1;
            }
            return count;
        }else{
            Long temp=(end +  start)/2;//如果不是最小單位,則繼續拆分
            ForkJoinDemo forkJoinDemo1 = new ForkJoinDemo(start, temp);//遞迴,拿到最新的start,end去看看是否要拆分
            ForkJoinDemo forkJoinDemo2 = new ForkJoinDemo(temp+1, end);//遞迴,拿到最新的start,end去看看是否要拆分
            forkJoinDemo1.fork();//拆分後的任務開始執行
            forkJoinDemo2.fork();//拆分後的任務開始執行
            return forkJoinDemo1.join()+forkJoinDemo2.join();//對結果進行彙總返回
        }
    }
}

Future(非同步回撥)

無返回值

CompletableFuture<Void> voidCompletableFuture = CompletableFuture.runAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(2);//延時兩秒鐘
                System.out.println("非同步執行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println("主執行緒執行");
        voidCompletableFuture.get();//等待非同步執行完成

有返回值

CompletableFuture<String> objectCompletableFuture = CompletableFuture.supplyAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "非同步執行結果";
        });
        System.out.println("主執行緒執行");
        System.out.println(objectCompletableFuture.get());