JUC學習總結

JUC相關基本概念

進程與線程

進程:進程是程序的一次執行過程,是系統運行程序的基本單位,系統運行一個程序後關閉,就是一個進程從開始到運行到死亡的全部過程。

線程:線程的粒度比進程更小,一個進程的執行過程會產生多個線程。所有線程會共享進程的堆內存,元空間區域,而每個線程都有自己的本地方法棧,虛擬機棧,程序計數器。由於系統在線程之間切換比在進程之間切換效率更高,因此線程也稱爲輕量級線程。

併發與並行

併發:同一間內多個線程搶奪同一資源,典型場景就是秒殺。

並行:同一段時間內多個線程同時在執行。

線程調用start方法後,並不是立即啓動,而是進入就緒狀態,還需要等待CPU調度時間片切換,拿到時間片後才切換成runnable狀態。

線程有哪幾種狀態?

new,runnable,blocked,wating,timed_wating,terminated

wating和timed_wating有什麼區別?

wating一直等,timed_wating有時間限制,只在規定時間內進行等待,過時不候。

wait和sleep都會導致線程進入Blocked狀態,這兩者有什麼區別呢?

當一個線程調用wait方法進入到Blocked狀態時,線程會釋放掉當前鎖的控制權;而調用sleep方法時則不會放掉當前鎖。

JAVA8的Lambda表達式簡介

Lambda表達式版的賣票程序

//賣票程序,高內聚低耦合下,線程操作資源類
class Tickets{
    private int num = 30;
    private Lock lock = new ReentrantLock();
    public  void sale(){
        lock.lock();
        try {
            if (num > 0){
                System.out.println(Thread.currentThread().getName() + "\t 賣出第 \t" + (num--) + "\t票,還剩\t" + num + "\t張票");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
public class SaleTest {
    public static void main(String[] args) {
        Tickets tickets = new Tickets();
        new Thread(() -> {for (int i = 0;i < 40;i++){tickets.sale();}},"A").start();
        new Thread(() -> {for (int i = 0;i < 40;i++){tickets.sale();}},"B").start();
        new Thread(() -> {for (int i = 0;i < 40;i++){tickets.sale();}},"C").start();
    }
}

lambda表達式要點

1、拷貝小括號,寫死右箭頭,落地大括號。

2、如果接口中只有一個方法即爲函數式接口,會默認添加@FunctionalInterface註解。

3、Java8後接口允許有多個default方法的實現,允許有多個靜態方法的實現。

//函數式接口:接口中只有一個待實現方法
interface SayHello{
    public int add(int x,int y);

    default int div(int x,int y){
        return x/y;
    }

    default int div2(int x,int y){
        return x/y;
    }

    public static int  mov(int x,int y){ 
        return x*y;
    }
    public static int  mov2(int x,int y){
        return x*y;
    }

}
public class LambdaExpressDemo {
    public static void main(String[] args) {
        SayHello sayHello = (x,y)->{return x + y;};
        System.out.println(sayHello.add(3,4));
        System.out.println(sayHello.div(4,4));
        System.out.println(SayHello.mov(3,4));
    }

}

synchronized版生產消費模型與ReentrantLock版

生產者消費者傳統版,由於判斷條件使用的是if,存在虛假喚醒的情況

//三大要點:判斷,工作,通信
class Air{
    private int num = 0;

    public synchronized void incr() throws InterruptedException {
        if (num != 0){
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "\t :" + num);
        this.notifyAll();
    }

    public synchronized void dec() throws InterruptedException {
        if (num == 0){
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "\t :" + num);
        this.notifyAll();
    }
}
public class AirContain {
    public static void main(String[] args) {
        Air air = new Air();
        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                try {
                    air.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                try {
                    air.dec();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();

    }
}

多線程四大要點:

1、高內聚低耦合前提下,線程操作資源類。

2、三大要點:判斷,工作,通信。

3、防止線程虛假喚醒(多線程中判斷不能用if,必須用while)。

4、標誌位。

class Air{
    private int num = 0;

    public synchronized void incr() throws InterruptedException {
        while (num != 0){
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + "\t :" + num);
        this.notifyAll();
    }

    public synchronized void dec() throws InterruptedException {
        while (num == 0){
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + "\t :" + num);
        this.notifyAll();
    }
}
public class AirContain {
    public static void main(String[] args) {
        Air air = new Air();
        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                try {
                    air.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                try {
                    air.dec();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                try {
                    air.incr();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                try {
                    air.dec();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"D").start();

    }
}

使用while替代if後,可以防止虛假喚醒的情況發生,但是如果想要精準喚醒(比如上面程序中,A線程執行後讓C線程執行)就實現不了。下面是使用ReentrantLock鎖的新版生產消費程序

class AirNew{
    private int num = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void incr(){
        lock.lock();
        try {
            while (num != 0){
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + "\t :" + num);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void dec(){
        lock.lock();
        try {
            while (num == 0){
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + "\t :" + num);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
public class AirContainNew {
    public static void main(String[] args) {
        AirNew airNew = new AirNew();
        new Thread(() -> {for(int i = 1;i<10;i++){airNew.incr();}},"A").start();
        new Thread(() -> {for(int i = 1;i<10;i++){airNew.dec();}},"B").start();
        new Thread(() -> {for(int i = 1;i<10;i++){airNew.incr();}},"C").start();
        new Thread(() -> {for(int i = 1;i<10;i++){airNew.dec();}},"D").start();
    }
}
class PrintABC{
//    標誌位
    private int num = 1;
   private Lock lock = new ReentrantLock();
// 創建3個condition,以達到精準喚醒的目的
   private Condition condition1 = lock.newCondition();
   private Condition condition2 = lock.newCondition();
   private Condition condition3 = lock.newCondition();

   public void A(){
       lock.lock();
       try {
           while (num != 1){
               condition1.await();
           }
           for (int i = 1;i<=5;i++){
               System.out.println(Thread.currentThread().getName()+"\t " + i);
           }
           num = 2;
           condition2.signal();
       }catch (Exception e){
           e.printStackTrace();
       }finally {
           lock.unlock();
       }
   }

    public void B(){
        lock.lock();
        try {
            while (num != 2){
                condition2.await();
            }

            for (int i = 1;i<=10;i++){
                System.out.println(Thread.currentThread().getName()+"\t " + i);
            }
            num = 3;
            condition3.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void C(){
        lock.lock();
        try {
            while (num != 3){
                condition3.await();
            }
            for (int i =1;i<=15;i++){
                System.out.println(Thread.currentThread().getName()+"\t " + i);
            }
            num = 1;
            condition1.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

}
public class PrintTen {
    public static void main(String[] args) {
        PrintABC printABC = new PrintABC();
        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                printABC.A();
            }
        },"A").start();

        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                printABC.B();
            }
        },"B").start();

        new Thread(() -> {
            for (int i = 1;i<=10;i++){
                printABC.C();
            }
        },"C").start();

    }
}

上述程序中使用ReentrantLock鎖配合newCondition()方法,創建多個condition,需要喚醒哪個condition時,直接調用其對應的signal()方法,達到精準喚醒的目的。每個Condition對象都包含着一個隊列(以下稱爲等待隊列),該隊列是Condition對象實現等待/通知功能的關鍵。condition的具體原理,日後深入瞭解了再述。

線程8鎖

根據下面的程序,有以下8個問題

1、標準訪問,請問先打印郵件還是短信?

2、郵件方法暫停4秒,請問先打印郵件還是短信?

3、新增一個普通方法hello(),請問先打印郵件還是hello?

4、兩部手機,請問先打印郵件還是短信?

5、兩個靜態同步方法,同一部手機,請問先打印郵件還是短信?

6、兩個靜態同步方法,2部手機,請問先打印郵件還是短信?

7、1個普通同步方法,1個靜態同步方法(郵件),1部手機,請問先打印郵件還是短信?

8、1個普通同步方法,1個靜態同步方法(郵件),2部手機(第一部調郵件,第二部調短信),請問先打印郵件還是短信?

class Phone{
    public synchronized void sendEmail() throws InterruptedException {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("------send email");
    }
    public synchronized void sendSMS(){
        System.out.println("send SMS");
    }
    public void hello(){
        System.out.println("hello");
    }
}
public class Lock8 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            try {
                phone.sendEmail();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"A").start();
        TimeUnit.SECONDS.sleep(2);
        new Thread(() -> {
            phone.sendSMS();
        },"B").start();

    }
}

1:先郵件,後短信。

2:先郵件,後短信。

3:先hello,後郵件。

4:先短信,後郵件。

5:先郵件,後短信。

6、先郵件,後短信。

7、先短信,後郵件。

8、先短信,後郵件。

8鎖小總結

synchronized實現同步的基礎:Java中的每一個對象都可以作爲鎖。且具體表現爲3種以下行式:

對於普通同步方法,鎖當前實例對象。

對於靜態同步方法,鎖當前類的class對象。

對於同步方法塊,鎖是synchonized括號裏面配置的對象。

一個線程如果有多個方法同時被synchronized修飾,某一時刻內,只要有一個線程A去訪問其中的一個synchronized修飾的方法,其他的線程不管是否訪問線程A訪問的方法,都需要等待這個線程A釋放鎖後,才能去訪問。換句話說,某一時刻內,只有能一個線程去訪問這些synchronized方法。因爲鎖的是當前對象this,被鎖定後,其他的線程都不能進入當前對象的其他synchronized方法。

當一個線程試圖訪問的同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。

也就是說如果一個實例對象的非靜態同步方法獲取鎖後,該實例對象的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖後,才能獲取鎖,可是別的實例對象的非靜態同步方法因爲跟該實例對象的非靜態同步方法用的是不同的鎖,所以無須等待該實例對象已獲取鎖的非驚天同步方法釋放鎖就可用獲取他們自己的鎖。

所有的靜態同步方法用的也是同一把鎖——類對象本身。

這兩把鎖(this和class)是兩個不同的對象,所以靜態同步方法與非靜態同步方法之間是不會有競爭關係的。但是一旦一個靜態同步方法獲取鎖後,其他的靜態同步方法都必須等待該方法釋放鎖後才能獲取鎖。而不管是同一個實例對象的靜態同步方法之間,還是不同的實例對象的靜態同步方法之間。只和他們是同一個類有關。

synchronized和Lock區別?

原始構成:synchronized是關鍵字屬於JVM層面依賴操作系統的調度,lock是juc包中具體的類屬於api層面的鎖。

使用方法:synchronized不需要手動釋放鎖,當synchronized代碼執行完後系統會自動讓線程釋放對鎖的佔用;lock則需要手動去釋放鎖,如果未釋放就有可能發生死鎖。

等待是否可中斷:synchronized不可中斷,除非拋出異常或者正常運行完成;lock可中斷,有兩種方法,第一種設置超時時間,第二種是調用interrupt()方法。

加鎖是否公平:synchronized是非公平鎖。lock兩者都可以,默認非公平鎖,構造方法可以傳入boolean值,true爲公平鎖,false爲非公平鎖。

鎖能否綁定多個條件:synchronized不可用,lock可以用來實現精確喚醒線程,而不是像synchronized要麼隨機喚醒,要麼喚醒全部線程。

list線程不安全問題

public class ListNotSafe {
    public static void main(String[] args) {
//        ArrayList list = new ArrayList<>();
//        List<Object> list = Collections.synchronizedList(new ArrayList<>());
        List<Object> list = new CopyOnWriteArrayList<>();

        for(int i = 0;i< 30;i++){
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0,3));
                System.out.println(list);
            },"A").start();

        }
    }
}

解決方案有3種:

1、用vector。

2、用Collections.synchronizedList方法包住list。

3、用CopyOnWriteArrayList。

CopyOnWrite容器即寫時複製的容器,往一個容器添加元素的時候,不直接往當前容器Object[]添加,而是先將當前容器Object[]進行Copy,複製出一個新的容器Object[] newElements,然後新的容器Object[] newElements裏添加元素,添加完元素之後,再將原容器的引用指向新的容器setArray(newElements);這樣做的好處是可用對CopyOnWrite容器進行併發的讀,而且不需要加鎖,因爲當前容器不會添加任何元素。所以CopyOnWrite容器也是一種讀寫分離的思想,讀和寫操作不同的容器。

CopyOnWriteArrayList的add方法源碼如下

    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

set線程不安全問題

同樣可用使用CopyOnWriteSet解決

HashSet底層是HashMap實現,add方法時是將值放進hashmap的key中,value是一個Obeject類型的常量

Callable接口與Runnable接口區別?

主要有3點區別,第一,callable接口帶返回值,第二Callable接口可以拋異常,第三Callable接口落地方法是call。

實現Callable接口的類,通過創建FutureTask對象後,將對象作爲參數傳入Thread構造方法進行調用。其中體現了多態的思想,因爲FutureTask實現了RunnableFuture接口,RunnableFuture同時實現了Runnalbe接口和Callable接口,而Thread類的構造器需要傳入Runnable類型的接口,所以可以傳入FutureTask對象。

Callable採用異步的思想,如果調用get方法獲取返回結果時,如果接口還沒算出來,那麼線程將會被掛起,執行其他的線程,直到計算結果完成,解除掛起線程,get方法得到返回結果。

class MyThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("call");
        return 1024;
    }
}
public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask(myThread);

        new Thread(futureTask,"A").start();
        new Thread(futureTask,"B").start();
        System.out.println(futureTask.get());
    }
}

CountDownLatch與CyclicBarrier

CountDownLatch用來使一個線程等待其他N個線程執行完畢之後,再執行。它主要又兩個方法,當一個或多個線程調用await方法時,這些線程會阻塞;其他線程調用countDown方法將計數器減1(調用countDown方法的線程不會阻塞);當計數器的值變爲0時,因await方法阻塞的線程會被喚醒繼續執行。

public class CountDownDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1;i<=6;i++){
            final int temp = i;
            new Thread(() -> {
                System.out.println("第" + temp + "個人出門");
                countDownLatch.countDown();

            },String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println("鎖門!");
    }
}

上面程序中,只有所有人都出門了,main線程才能鎖門,main線程等待其他都執行完了再執行。下面再看看CyclicBarrier。

CyclicBarrier用來使所有線程都到達指定狀態後,再繼續下面的操作,各個線程彼此等待,所有線程都執行完成後,才繼續後面的操作。

public class CyclicBarrierDemo {
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召喚神龍!");
        });
        for (int i = 1;i<=7;i++){
            final int  temp = i;
            new Thread(() -> {
                System.out.println("集齊第" + temp +"顆龍珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();
        }
    }
}

Semaphore

在信號量上有兩種操作:

acquire(獲取),當一個線程調用acquire操作時,它要麼通過成功獲取信號量(信號量減1),要麼一直等下去,直到有線程釋放信號量,或超時。

release(釋放)實際上會將信號量的值加1,然後喚醒等待的線程。

信號量主要用於兩個目的,一個是用於多個共享資源的互斥使用,另一個用於併發線程數的控制。

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);//初始3個車位

        for (int i = 1;i <= 10;i++){
            new Thread(() -> {
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "號線程搶到了車位,停車3秒鐘");
                    TimeUnit.SECONDS.sleep(3);
                    System.out.println(Thread.currentThread().getName()+ "號線程把車開走了......");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }

            },String.valueOf(i)).start();

        }
    }
}

ReenReadWriteLock

多個線程同時讀一個資源類沒有任何問題,所以爲了滿足併發量,讀取共享資源應該可以同時進行。但是如果有一個線程想要去寫共享資源,就不應該再有其他線程可以對該資源進行讀或寫(不能讀是爲了保證數據一致性)。

class MyCache{
    private volatile HashMap map = new HashMap();
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    public  void put(String key,String value){
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "號線程開始寫數據。。。。");
            TimeUnit.MILLISECONDS.sleep(300);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName() + "號線程寫數據完成-----------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.writeLock().unlock();
        }
    }
    public  void get(String key){
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "號線程開始讀數據~~~~~");
            TimeUnit.MILLISECONDS.sleep(300);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "號線程讀到數據:\t" + o);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.readLock().unlock();
        }
    }
}
public class ReadWriteLockDemo {
    public static void main(String[] args) throws InterruptedException {
        MyCache myCache = new MyCache();
        for (int i = 1;i<= 6;i++){
            final int temp = i;
            new Thread(() -> {
                myCache.put(temp + "",temp+"");
            },String.valueOf(i)).start();
        }
        for (int i = 1;i<= 6;i++){
            final int temp = i;
            new Thread(() -> {
                myCache.get(temp + "");
            },String.valueOf(i)).start();
        }
    }
}

小總結:

讀-讀能共存

讀-寫不能共存

寫-寫不能共存

阻塞隊列

當隊列是空的,向隊列中獲取元素的線程會阻塞

當隊列是滿的,向隊列中添加元素的線程會阻塞

在多線程領域:所謂阻塞,是指在某些情況下將掛起線程;而當滿足某些條件時,再將掛起的線程自動喚醒。

爲什麼需要BlockingQueue?

好處是我們不需要關心什麼時候需要阻塞線程,什麼時候需要喚醒線程,因爲這一切BlockingQueue都已經實現了。在JUC包發佈以前,多線程環境下,我們不僅需要自己去控制這些細節,還要兼顧效率和線程安全,而這會給我們的程序帶來不小的複雜度。

ArrayBlockingQueue:由數組結構組成的有界阻塞隊列。

LinkedBlockingQueue:由鏈表結構組成的有界(大小默認值爲integer.MAX_VALUE)阻塞隊列。

SynchronousQueue:不存儲元素的阻塞隊列,也即單個元素的隊列。

線程池

10年前單核CPU電腦,假的多線程,像馬戲團小丑玩多個球,CPU需要來回切換

限制是多核電腦,多個線程各自跑在獨立的CPU上,不用切換效率高。

線程池的優勢:

線程池做的工作只需要控制運行的線程數量,處理過程中將任務放入隊列,然後在線程創建後啓動這些任務,如果線程數量超過了最大數量,超出數量的線程排隊等候,等其他線程執行完畢,再從隊列中取出任務來。

它的主要特點爲:線程服用;控制最大併發數;管理線程。

第一:降低資源消耗。通過重複利用已創建的線程降低線程創建和銷燬造成的消耗。

第二:提高響應速度。當任務到達時,任務可以不需要等待線程創建就能立即執行。

第三:提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配,調優和監控。


JUC自帶的三種線程池如下圖所示

線程池7大參數

corePoolSize:核心線程數。

maximumPoolSize:線程池中最大線程數,必須大於1。

keepAliveTime:非核心線程存活時間,當線程池的數量超過corePoolSize時,如果多餘的線程在keepAliveTime的時間內空閒,就會被銷燬。

unit:非核心線程存活時間單位。

workQueue:存放被提交但還未執行任務的阻塞隊列。

threadFactory:創建線程的工廠,一般都用默認。

handler:線程池拒絕策略。表示當隊列滿了,而且工作線程數等於maximumPoolSize時,如何來拒絕請求的策略。

    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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

線程池執行流程如下圖所示

線程池運行流程:

1、在創建了線程池後,開始等待請求。

2、當調用execute()方法添加一個請求任務時,線程池會做出如下判斷:

 2.1、如果正在運行的線程數量小於corePoolSize,那麼馬上創建線程運行這個任務;

 2.2、如果正在運行的線程數量大於或等於corePoolSize,那麼將這個任務放入隊列;

 2.3、如果這個時候隊列滿了且正在運行的線程數量還小於maximumPoolSize,那麼還是要創建非核心線程立刻執行這個任務;

 2.4、如果隊列滿了,且正在運行的線程數大於或等於maximumPoolSize,那麼線程池會啓動飽和拒絕策略來執行。

3、當一個線程完成任務時,它會從隊列中取下一個任務來執行。

4、當一個線程無事可做超過一定的時間(keepAliveTime)時,線程會判斷:如果當前運行的線程數大於corePoolSize,那麼這個線程就會被停掉。所有線程池的所有任務完成後,它最終會收縮到corePoolSize的大小。

線程池不允許使用Executors去創建,而是通過ThreadPoolExecutor的方式,這樣處理的方式是讓寫的人更加明確線程池的運行規則,規避資源耗盡的風險。

Executors返回的線程池對象的弊端如下:

1、FixedThreadPool和SingleThreaadPool:允許的請求隊列長度爲Integer.MAX_VALUE,可能會堆積大量的請求,從而導致OOM。

2、CachedThreadPool和ScheduledThreadPool:允許的創建線程數量爲Integer.MAX_VALUE,可能會創建大量的線程,從而導致OOM。

等待隊列已經滿了,再也塞不下新任務了,同時,線程池中的max線程數也到了,無法繼續爲新任務服務。這個時候我們就需要拒絕策略機制合理的處理這個問題。

線程池拒絕策略

AbortPolicy(默認):直接拋出RejectedExecutionException異常阻止系統正常運行。

CallerRunsPolicy:調用者運行一種調節機制,該策略既不會拋棄任務,也不會拋出異常,而是將某些任務退回到調用者,從而降低新任務的流量。

DiscardOldestPolicy:拋棄隊列中等待最久的任務,然後把當前任務加入隊列中嘗試再次提交當前任務。

DiscardPolicy:該策略默默的丟棄無法處理的任務,不予任何處理也不拋出異常。如果允許任務丟失,這是最好的一種策略。

分支合併框架

Fork用來把一個大任務化小。

Join用來把拆分的任務進行合併

ForkJoinPool繼承樹如下圖所示

ForkJoinTask

RecursiveTask用來實現遞歸任務,即自己調用自己。

下面程序使用Fork計算1累加到100

class MyFork extends RecursiveTask<Integer> {
    private int ADD_PARM_CONTROL = 10;
    private int begin;
    private int end;

    public MyFork(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        int result = 0;
        if (end - begin <= ADD_PARM_CONTROL){
            for (int i=begin;i <= end;i++){
                result = result + i;
            }
        }else {
            int mid = (end + begin) / 2 ;
            MyFork myFork1 = new MyFork(begin, mid);
            MyFork myFork2 = new MyFork(mid + 1, end);
            ForkJoinTask<Integer> fork1 = myFork1.fork();
            ForkJoinTask<Integer> fork2 = myFork2.fork();
            result = fork1.join()+fork2.join();
        }
        return result;
    }
}
public class ForkJoinDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyFork myFork = new MyFork(1, 100);
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Integer> submit = forkJoinPool.submit(myFork);
        System.out.println(submit.get());
    }
}

流式計算

流是數據渠道,用於操作數據源(集合、數組等)所生成的元素序列

集合講的是數據,流講的是計算。

流的特點:stream自己不能存儲元素,不會改變源對象。相反,他們會返回一個持有結果的新stream。stream操作是延遲執行的,這意味着他們會等到需要結果的時候才執行。

stream有3個階段

創建stream:一個數據源(數組或集合)

中間操作:一箇中間操作,處理數據源數據

終止操作:一個終止操作,執行中間操作連,產生結果

public class StreamDemo {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,5);

        List<Integer> streamList = list.stream().filter(num -> {
            return num % 2 == 0;
        }).map(num -> {
            return num / 2;
        }).collect(Collectors.toList());
        System.out.println(streamList);

        Map<Integer, Integer> collect = list.stream().filter(num -> {
            return num % 2 != 0;
        }).map(x -> {
            return x * 2;
        }).collect(Collectors.toMap(x -> {
            return x;
        }, y -> {
            return y * 2;
        }));
        System.out.println(collect);
    }
}
class User implements Comparable<User>{
    private int id;
    private String name;
    private int age;

    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public int compareTo(User o) {
        return age - o.getAge();
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}public class StreamDemo {
    public static void main(String[] args) {
        User u1 = new User(1, "a", 23);
        User u2 = new User(2, "b", 24);
        User u3 = new User(3, "c", 25);
        User u4 = new User(4, "d", 26);
        User u5 = new User(5, "e", 27);

//        按照給出數據,找出同時滿足以下條件的用戶,也即以下條件全部滿足:
//        偶數ID且年齡大於等於24,且用戶名轉爲大寫,且用戶年齡最大的一個用戶
        List<User> users = Arrays.asList(u1, u2, u3, u4, u5);
        List<User> collect = users.stream()
                .filter(user -> {
                    return user.getId() % 2 == 0;
                })
                .filter(user -> {
                    return user.getAge() >= 24;
                })
                .map(user -> {
                    return new User(user.getId(),user.getName().toUpperCase(),user.getAge());
                }).sorted((o1, o2) -> {
                    return o2.compareTo(o1);
                })
                .limit(1).collect(Collectors.toList());
        System.out.println(collect);
    }
}

 

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