主頁 >  其他 > JAVA多執行緒之JUC總結

JAVA多執行緒之JUC總結

2021-01-14 11:50:52 其他

1.前置知識

1.1.執行緒與行程

行程是一個具備獨立功能的程式的一次動態執行的程序,是作業系統進行資源分配和調度的獨立單位,

早期的作業系統是沒有執行緒這個概念的,行程就是擁有資源的運行的最小單位,也是程式運行的最小單位,隨著計算機的發展,行程的背景關系切換開銷較大

所以發明了一個新的概念“執行緒”,執行緒是程式執行中一個單一的順序執行流程,是程式執行的最小的單位,是處理器調度的分配的基本單位,

總結:

1.執行緒是執行的最小單位,行程是作業系統分配資源的最小單位

2.一個行程由一個執行緒或者多個執行緒組成,執行緒是一個行程中代碼的不同執行路線(多執行緒)

3.執行緒的調度和切換比行程快得多

行程可以類比為QQ.exe,而執行緒則是QQ的聊天功能,視頻功能的執行程序

1.2并行與并發

并行是指當系統有多個cpu的時候,多個執行緒可以在不同的cpu上同時執行,兩個行程互不搶占cpu資源,可以同時進行

并發是指在一個時間段中,有多個行程都處于已啟動到運行完畢的程序中,它們搶占cpu資源,cpu將一個時間段劃分成為一個個的時間片(時間區間),然后在這個時間片中進行行程的來回切換執行,

1.3執行緒的狀態

Thread.State
 
 
 
public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW,(新建)

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,(準備就緒)

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,(阻塞)

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,(不見不散)

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,(過時不候)

    /**
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;(終結)
}
 
 

2.Lock介面

2.1多執行緒編程模板

進行多執行緒編程的時候,在高內聚低耦合的情況下,撰寫執行緒 操作 資源類,例子代碼在下

class Ticket {/*資源類*/
    private Lock lock = new ReentrantLock();/*可重入鎖*/
    private int number = 300;

    public void sale() {//操作(對外暴露的呼叫方法)
        try {
            lock.lock();
            if (number > 0) {
                System.out.println(Thread.currentThread().getName() + "賣出第" + number-- + "張票,還剩下" + number + "張票");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

}

public class SaleTicket {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        //執行緒
        new Thread(() ->{ for (int i = 0; i < 300; i++) ticket.sale(); },"A").start();
        new Thread(() ->{ for (int i = 0; i < 300; i++) ticket.sale(); },"B").start();
        new Thread(() ->{ for (int i = 0; i < 300; i++) ticket.sale(); },"C").start();
    }
}

2.2傳統Synchroinezd實作執行緒的通信

首先需要明白兩個概念:

1.等待池:當執行緒A呼叫某個物件的wait方法后,那么執行緒A就會釋放鎖并進入該物件的等待池,在等待池中的執行緒是不會去競爭鎖的,

2.鎖池:當某個執行緒呼叫該物件的notifyAll()方法時,處于該物件等待池中的所有物件都會進入鎖池,鎖池中的執行緒會去競爭鎖,

在執行緒通信的時候 撰寫代碼口訣  判斷 干活 通知

多執行緒互動中,必須防止多執行緒的虛假喚醒,也即(判斷只能用while,不能用if,根本原因是if之后,不會再進行判斷)

下方的例子中如果使用if判斷,當當前number值是1時,喚醒執行緒可能會喚醒執行add方法的執行緒,而且add方法的執行緒不會再執行判斷,所以會增加為2,導致錯誤

例子代碼實作對一個變數進行加一減一,

class AirConditioner{
    private int number = 0;
    public synchronized void add(){
        //判斷(滿足條件跳出回圈)
        while (number != 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //干活(執行業務邏輯)
        number ++;
        System.out.println(Thread.currentThread().getName() + number);

        //通知(喚醒其他執行緒)
        this.notifyAll();
    }
    public synchronized void dec(){
        //判斷
        while (number == 0) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //干活
        number --;
        System.out.println(Thread.currentThread().getName() + number);

        //通知
        this.notifyAll();
    }
}
public class ThreadWaitNotifyDemo {
    public static void main(String[] args) {
        AirConditioner conditioner = new AirConditioner();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                conditioner.add();
            }

        },"同學A").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                conditioner.dec();
            }
        },"同學B").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                conditioner.add();
            }

        },"同學C").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                conditioner.dec();
            }
        },"同學D").start();
    }
}

2.3Lock實作執行緒的通信

首先看下圖

使用Lock后,synchronized中的wait和notify被await和signal方法代替了,

而下面代碼的Condition又是什么呢?還記得剛剛講的等待池和鎖池的概念嗎,他們有異曲同工之妙,但是Condition的功能更加強大,

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

    public void add() {
        lock.lock();
        try {
            //判斷
            while (number != 0) {
                condition.await();
            }
            //干活
            number++;
            System.out.println(Thread.currentThread().getName() + number);
            //通知
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void dec() {
        lock.lock();
        try {
            //判斷
            while (number == 0){
                condition.await();
            }
            //干活
            number --;
            System.out.println(Thread.currentThread().getName() + number);
            //通知
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

public class ThreadWaitNotifyDemo2 {
    public static void main(String[] args) {
        AirConditioner2 conditioner = new AirConditioner2();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                conditioner.add();
            }

        }, "同學A").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                conditioner.dec();
            }
        }, "同學B").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                conditioner.add();
            }

        }, "同學C").start();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                conditioner.dec();
            }
        }, "同學D").start();
    }
}

2.4Lock實作精準喚醒執行緒

第一個問題:如何保證順序

第二個問題:如何保證喚醒的執行緒

解決:第一個問題使用一個標志位,定義一個變數來充當標志位,判斷時使用該變數來判斷屬于哪一個執行緒

第二個問題可以使用Condition來實作精準喚醒,condition都有自己的等待佇列,當await的時候,就將該執行緒放入自己的等待佇列中,當使用signal()的時候喚醒當前condition佇列中的執行緒

/* 多個執行緒之間按順序呼叫,實作A->B->C
* AA列印五次,BB列印十次,CC列印十五次
* 接著
* AA列印五次,BB列印十次,CC列印十五次
來十輪*/ 

class ShareResource{
    private int number = 1;//A:1 B:2 C:3 標志位
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    /*設定三個condition是為了更加精確提升效率,每個condition都有自己的等待佇列,當await的時候,就將該執行緒放入自己的等待佇列中,
    可以用當前condition.singal方法進行精確喚醒*/
    public void print5(){
        lock.lock();
        try {
            //判斷
            while (number != 1){
                condition1.await();
            }
            //干活
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            //通知
            number = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print10(){
        lock.lock();
        try {
            //判斷
            while (number != 2){
                condition2.await();
            }
            //干活
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            //通知
            number = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print15(){
        lock.lock();
        try {
            //判斷
            while (number != 3){
                condition3.await();
            }
            //干活
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            //通知
            number = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
public class ZZ01ThreadOrderAccess {

    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                shareResource.print5();
            }
        },"A").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                shareResource.print15();
            }
        },"C").start();
        new Thread(()->{
            for (int i = 0; i <10 ; i++) {
                shareResource.print10();
            }
        },"B").start();

    }
}

總結
1.首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;
2.synchronized無法判斷是否獲取鎖的狀態,Lock可以判斷是否獲取到鎖;
3.synchronized會自動釋放鎖(a 執行緒執行完同步代碼會釋放鎖 ;b 執行緒執行程序中發生例外會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成執行緒死鎖;
4.用synchronized關鍵字的兩個執行緒1和執行緒2,如果當前執行緒1獲得鎖,執行緒2執行緒等待,如果執行緒1阻塞,執行緒2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,執行緒可以不用一直等待就結束了;
5.synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可)
6.Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題,

3.執行緒安全的集合類

3.1CopyOnWriteArrayList

先看看我們平時使用的ArrayList是如何在多執行緒環境報例外的

public static void listNotSafe1() {
        //List<String> list = Collections.synchronizedList(new ArrayList<>());
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }

上述代碼執行程序中會出現java.util.ConcurrentModificationException(并發修改例外)

導致此例外的原因是什么呢?

看看報錯資訊是迭代器出現了例外

private class Itr implements Iterator<E> {
        int cursor;       //下一個要訪問元素的索引值
        int lastRet = -1; //上一個訪問元素的索引值
        int expectedModCount = modCount;//修改次數的期望值,modCount表示ArrayList被修改的次數

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        @Override
        @SuppressWarnings("unchecked")
        public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;
            checkForComodification();
        }

        final void checkForComodification() {
            //當對list的次改次數不等于期望的修改次數后,就會產生這個例外,比如多執行緒擴容的情況下
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

我們來看看執行緒安全的集合類

使用CopyOnWriteArrayList可以實作

public static void listNotSafe() {
        //List<String> list = Collections.synchronizedList(new ArrayList<>());
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }

看看CopyOnWriteArrayList是如何實作并發修改的(保證高并發的讀,和寫的時候資料一致性),看看添加元素的原始碼

CopyOnWriteArrayList是一個寫時復制的思想,當向容器中添加元素的時候,并不會向原容器中直接添加,而是復制出一個新的陣列,將原有容器的元素添加到新的陣列

Object[] newElements 中,并且長度為原有容器長度+1,然后向新的容器添加元素,最后setArray(newElements),將原容器的參考指向新的容器,

這樣做的好處是,可以進行并發的讀,而不需要加鎖,只有對元素進行增刪時才會進行加鎖操作,所以CopyOnWriteArrayList也會一種讀寫分離的思想,讀和寫都是不同的容器

3.2CopyOnWriteArraySet

說句題外話,HashSet底層是HashMap,那么value值是什么呢?

value值是一個Object物件,為什么設定為Object,而不是null呢,null占用空間小,而且效率更高,究竟是為什么呢?

我們先來看看HashSet的remove方法

public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }

可以看到它呼叫的是HashMap的remove方法,來吧,看看HashMap的remove方法

public V remove(Object key) {
        Node<K,V> e;
        return (e = removeNode(hash(key), key, null, false, true)) == null ?
            null : e.value;
    }

看到這應該就明白了,當洗掉某一個資料時,會回傳被洗掉資料的value,如果HashSet的value設定為null的話,洗掉資料永遠回傳null,從而無法判斷是否洗掉成功,

CopyOnWriteArraySet又是如何把保證執行緒安全的呢?

看看它的構造方法

public class CopyOnWriteArraySet<E> extends AbstractSet<E>
        implements java.io.Serializable {
    private static final long serialVersionUID = 5457747651344034263L;

    private final CopyOnWriteArrayList<E> al;

    /**
     * Creates an empty set.
     */
    public CopyOnWriteArraySet() {
        al = new CopyOnWriteArrayList<E>();
    }
}

可以看到,它的底層也是使用的CopyOnWriteArrayList,接下來就不用多說了吧

3.3ConcurrentHashMap

請關注后續帖子

4.Callable介面

既然有了Runnable介面為什么還需要Callable介面呢?

來看看吧

class MyThread implements Runnable{

    @Override
    public void run() {

    }
}
class MyThread2 implements Callable<Integer> {


    @Override
    public Integer call() throws Exception {
        System.out.println("***********come in");
        return 1024;
    }
}

從上述代碼段可以看出run方法沒有回傳值,且不能拋出例外,而call方法有回傳值,而且能拋出例外,是否感覺很強大呢?

那么如何傳入Callable呢?看他的繼承樹,它和Runnable介面有沒有任何關系

我們可以先看看Runnable介面

它有一個實作類,是FutureTask,而FutureTask剛好可以傳入一個Callable,

好了,這下我們知道怎么寫代碼了

public class ZZ04CallableDemo {
    public static void main(String[] args)
            throws ExecutionException, InterruptedException {
        MyThread2 thread2 = new MyThread2();
        FutureTask futureTask = new FutureTask(thread2);
        new Thread(futureTask,"a").start();
        System.out.println(futureTask.get());
    }
}

這下就可以傳入了,

那么Callable究竟是為了解決什么樣的問題而存在呢?

重點就是回傳值,可以獲取在不同的計算場景中獲取他們的回傳值,成功或失敗等

以上述代碼為例

當在主執行緒中執行比較耗時的任務時,又不想阻塞主執行緒,可以將這些任務交給Callable在后臺完成,當主執行緒將來需要時,就可以通過Future物件獲得后臺作業的計算結果或者執行狀態,

一般FutureTask多用于耗時的計算,主執行緒可以在完成自己的任務后,再去獲取結果,

重點:僅在計算完成時才能檢索結果;如果計算尚未完成,則阻塞 get 方法,一旦計算完成, 就不能再重新開始或取消計算,get方法而獲取結果只有在計算完成時獲取,否則會一直阻塞直到任務轉入完成狀態, 然后會回傳結果或者拋出例外,

也就是說呼叫get方法時,如果get方法未執行完畢,那么主執行緒也會被阻塞,所以說get方法最好是放在最后執行,

5.常用工具類

5.1CountDownLatch

計數器

例子代碼:教室里有6個學生,必須等6個學生全部離開教室,教室門才可以關閉

public class ZZ05CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 0; i <6 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"離開教室");
                countDownLatch.countDown();
            },String.valueOf(i)).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"班長關門走人");
    }
}

CountDownLatch主要有兩個方法:

countDown():其它執行緒呼叫countDown方法會將計數器減1(呼叫countDown方法的執行緒不會阻塞)

await():當一個或多個執行緒呼叫await方法時,這些執行緒會阻塞,當計數器的值變為0時,因await方法阻塞的執行緒會被喚醒,繼續執行,

5.2CyclicBarrier

可回圈使用的屏障

例子代碼:當阻塞執行緒達到7個,才可以召喚神龍

public class ZZ06CycliBarrierDemo {
    public static void main(String[] args) throws BrokenBarrierException, InterruptedException {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            {
                System.out.println("召喚神龍");
            };
        });
        for (int i = 0; i < 7; i++) {
            final int tempInt = 1;
            new Thread(()->{

                System.out.println(Thread.currentThread().getName()+"收集到第" + tempInt +  "顆龍珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)).start();

        }

    }
}

CyclicBarrier內部有一個方法

await():當前執行緒進入阻塞狀態(進入屏障)

只有當最后一個執行緒到達屏障點,匿名內部類的方法和所有被屏障攔截的執行緒才會繼續執行,

5.3Semaphore

信號量

例子代碼:6輛車搶占3個車位

public class ZZ07SemaphoreDemo {
    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(3);
                    System.out.println(Thread.currentThread().getName() + "離開了車位");
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }

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

    }
}

Semphore內部有兩個方法

acquire():獲取,當執行緒呼叫此方法時,要么成功獲取信號量(信號量-1),要么一直阻塞,直到有執行緒釋放信號量或者超時

release():釋放,當執行緒呼叫此方法時,會釋放信號量(信號量+1),然后喚醒阻塞的執行緒,

Semphore的使用主要有兩個目的,一個是設定多執行緒的互斥使用,一個是控制并發量

6.ReadWriteLock

在傳統的高并發情況下,不管讀和寫都會加鎖,其他執行緒只能等到獲取到鎖后才能去執行自己的操作,這樣的話如果只是讀取操作,也會導致效率變慢

所以誕生了讀寫鎖,只有讀操作時為了滿足高并發只是加共享鎖,此時讀操作允許多個執行緒同時訪問,當某一個執行緒拿到寫鎖時,此時會加排它鎖,除此執行緒外所有執行緒都不能讀寫

總而言之就是 讀-讀 可以共存 讀-寫 不可以共存 寫-寫 不可以共存

class Cache {
    private volatile Map<String, Object> map = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    public void put(String key, Object value) {
        readWriteLock.writeLock().lock();
        System.out.println(Thread.currentThread().getName() + "寫入資料"+ value );
        try {
            TimeUnit.MICROSECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "寫入完成");
        readWriteLock.writeLock().unlock();
    }

    public void get(String key) {
        readWriteLock.readLock().lock();
        System.out.println(Thread.currentThread().getName() + "讀取了資料");
        try {
            TimeUnit.MICROSECONDS.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName() + "讀取完成" + o);
        readWriteLock.readLock().unlock();
    }
}

public class ZZ08ReadOnWriteLockDemo {
    public static void main(String[] args) {
        Cache cache = new Cache();
        for (int i = 0; i < 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                cache.put(tempInt+"",tempInt + "");

            }, String.valueOf(i)).start();
        }
        for (int i = 0; i < 5; i++) {
            final int tempInt = i;
            new Thread(() -> {
                cache.get(tempInt+"");
            }, String.valueOf(i)).start();
        }
    }
}

底層原始碼請關注后續AQS解讀

7.BlockingQueue(阻塞佇列)

阻塞佇列是用于在高并發情況下不得不阻塞執行緒的時候,如何管理執行緒和資源的一種手段

Thread1向阻塞佇列中添加元素,Thread2從佇列中取走元素

當佇列是空的,從對列中取出元素的操作將會被阻塞

當佇列是滿的,向對列中添加元素的操作將會被阻塞

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

試圖向已滿的佇列中添加新元素的執行緒將會被阻塞,直到其他執行緒從佇列中移除一個或多個元素或者完全清空,使佇列變得空閑起來并后續新增

阻塞佇列的優勢:

不需要關心什么時候需要阻塞執行緒,什么時候需要喚醒執行緒,因為一切都由BlockingQueue包辦了,

首先看看阻塞佇列的繼承樹

ArrayBlockQuque:由陣列結構組成的有界的阻塞佇列

LinkedBlockingQueue:由鏈表結構組成的有界(界限為Integer.MAX_VALUE)的阻塞佇列

SynchronousQueue:不存盤元素的阻塞佇列,也就是單個元素的佇列

LinkedBlockingDeque:由鏈表結構組成的有界(界限為Integer.MAX_VALUE)的雙向阻塞佇列

具體的方法見下

public class ZZ09BlockingQueueDemo {

    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockingQueue= new ArrayBlockingQueue<>(3);
        /*此組add方法與remove,超出佇列界限會拋出例外
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
        blockingQueue.remove();
        blockingQueue.remove();
        blockingQueue.remove();
        blockingQueue.remove();
        System.out.println(blockingQueue.add("d"));*/
        /*System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.element());//回傳佇列第一個元素*/

        /*此組offer與poll方法超出佇列界限不會拋出例外只回傳false和null
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        System.out.println(blockingQueue.offer("x"));
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());*/

        /*put,當佇列沒有滿,可以往里添加,滿了后續的put操作會被阻塞*/
       /*blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        blockingQueue.put("d");
        System.out.println(blockingQueue.take());

        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());*/

       /* System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b", 3L, TimeUnit.SECONDS));*/
    }
}

8.執行緒池

什么是執行緒池?

執行緒池是一種多執行緒處理任務的一種方式,處理程序中將任務添加到佇列(也就是我們上一講的阻塞佇列中),然后這些執行緒自動啟動這些任務

執行緒池的主要作業就是控制運行的執行緒數量,處理程序中將任務放入阻塞佇列,然后在執行緒創建后去執行這些任務,如果執行緒數量超過了最大數量,超出數量的執行緒排隊等候,等待其他執行緒執行完畢,再從佇列中取出任務來執行

主要特點:

執行緒復用

控制最大并發數

管理執行緒,

優勢:

1.重復利用已經創建的執行緒,降低執行緒創建和銷毀所帶來的開銷

2.任務可以不需要等待執行緒創建就可以執行,從而提高回應速度

3.提高執行緒的可管理性,使用執行緒池可以進行集中的監控,分配和調優,

看看Java中執行緒池的繼承樹

Executors類似于Collections,它作為執行緒池的工具類為我們提供了很多強大的功能

例子代碼如下:

public class ZZ11MyThreadPoolDemo {
    public static void main(String[] args) {
        /*
        Executors 輔助工具類,類似于collections
        * */
       /* ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池五個執行緒,類似于銀行有五個受理視窗
        ExecutorService threadPool = Executors.newSingleThreadExecutor();*///一個池子一個執行緒,類似于銀行有一個受理視窗
        //一池子N個作業執行緒,類似于銀行有N個受理視窗,相當于前兩個執行緒池的結合體,
        //執行很多任務時可以使用,執行緒池根據任務需要進行創建執行緒,但是在先前的執行緒可用的時候重用他們,在不可用的時候新建執行緒(擴容)
        //ExecutorService threadPool = Executors.newCachedThreadPool();

        //自定義執行緒池
        ExecutorService threadPool = new ThreadPoolExecutor
                (2, 5, 2L, TimeUnit.SECONDS,
                        new LinkedBlockingQueue<>(3),
                        Executors.defaultThreadFactory(),
                        new ThreadPoolExecutor.DiscardOldestPolicy());
        try {

            /*模擬十個顧客過來辦理業務,目前池子只有五個作業人員提供服務*/
            for (int i = 0; i < 9; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "辦理業務");

                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

我們看看它們是如何創建執行緒池的

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

可以看到在原始碼中,ThreadPoolExecutor中有七個引數

int corePoolSize 常駐核心執行緒數

int maximumPoolSize 執行緒池中能容納同時執行的最大執行緒數

long keepAliveTime 多余空閑執行緒的存活時間,如果達到,執行緒會被銷毀

TimeUnit unit keepAliveTime的單位

BlockingQueue<Runnable> workQueue 任務佇列,被提交但是尚未被執行的任務

ThreadFactory threadFactory 創建執行緒的工廠

RejectedExecutionHandler handler 拒絕策略,當佇列滿了,并且作業執行緒大于等于執行緒池的最大執行緒數時如何來拒絕請求執行的runnable的策略

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/248547.html

標籤:其他

上一篇:RSA演算法密鑰長度的選擇

下一篇:網路增大

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more