主頁 > 後端開發 > 2.6W + 字,徹底搞懂 JUC!

2.6W + 字,徹底搞懂 JUC!

2021-10-27 11:08:34 後端開發

來源:blog.csdn.net/wangwenpeng0529/article/details/105769978

簡介

在 Java 5.0 提供了 java.util.concurrent(簡稱JUC)包,在此包中增加了在并發編程中很常用的工具類,用于定義類似于執行緒的自定義子系統,包括執行緒池,異步 IO 和輕量級任務框架;還提供了設計用于多執行緒背景關系中的 Collection 實作等

volatile 關鍵字

記憶體可見性

記憶體可見性(Memory Visibility)是指當某個執行緒正在使用物件狀態而另一個執行緒在同時修改該狀態,需要確保當一個執行緒修改了物件狀態后,其他執行緒能夠看到發生的狀態變化,

可見性錯誤是指當讀操作與寫操作在不同的執行緒中執行時,我們無法確保執行讀操作的執行緒能適時地看到其他執行緒寫入的值,有時甚至是根本不可能的事情,

我們可以通過同步來保證物件被安全地發布,除此之外我們也可以使用一種更加輕量級的 volatile 變數,

Java 提供了一種稍弱的同步機制,即 volatile 變數,用來確保將變數的更新操作通知到其他執行緒,可以將 volatile 看做一個輕量級的鎖,但是又與鎖有些不同:

  • 對于多執行緒,不是一種互斥關系
  • 不能保證變數狀態的“原子性操作

問題代碼示例

/**
 * @ClassName TestVolatile
 * @Description: Thread 已經修改了flag,但是main執行緒還是拿到的false
 * @Author: WangWenpeng
 * @Version 1.0
 */
public class TestVolatile {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while (true) {
            if (td.isFlag()) {
                System.out.println("______________");
                break;
            }
        }
    }
}

class ThreadDemo implements Runnable {
    private boolean flag = false;

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        try {
            //增加這種出現問題的幾率
            Thread.sleep(200);
        } catch (Exception e) {
        }
        flag = true;
        System.out.println("flag=" + isFlag());
    }
}

兩個執行緒同時修改這一個flag,為什么main拿到的還是這種修改之前的值

記憶體分析

解決方法,加鎖

public class TestVolatile {
    public static void main(String[] args) {
        ThreadDemo td = new ThreadDemo();
        new Thread(td).start();
        while (true) {
            synchronized (td) {
                if (td.isFlag()) {
                    System.out.println("______________");
                    break;
                }
            }
        }
    }
}

加了鎖,就可以讓while回圈每次都從主存中去讀取資料,這樣就能讀取到true了,但是一加鎖,每次只能有一個執行緒訪問,當一個執行緒持有鎖時,其他的就會阻塞,效率就非常低了,不想加鎖,又要解決記憶體可見性問題,那么就可以使用volatile關鍵字,

volatile

private volatile boolean flag = false;

volatile 關鍵字:當多個執行緒進行操作共享資料時,可以保證記憶體中的資料可見,相較于 synchronized 是一種較為輕量級的同步策略,

注意:

  • volatile 不具備“互斥性”
  • volatile 不能保證變數的“原子性”

原子性

所謂原子性就是操作不可再細分

問題代碼

package com.atguigu.juc;

/**
 * @ClassName TestAtomicDemo
 * @Description:
 * @Author: WangWenpeng
 * @Version 1.0
 */
public class TestAtomicDemo {
    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(ad).start();
        }
    }
}

class AtomicDemo implements Runnable {

    private int serialNumber = 0;

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }
        System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
    }

    public int getSerialNumber() {
        return serialNumber++;
    }
}

看到這里,好像和上面的記憶體可見性問題一樣,是不是加個volatile關鍵字就可以了呢?其實不是的,因為加了volatile,只是相當于所有執行緒都是在主存中操作資料而已,但是不具備互斥性,比如兩個執行緒同時讀取主存中的0,然后又同時自增,同時寫入主存,結果還是會出現重復資料,

import java.util.concurrent.atomic.AtomicInteger;

/**
 * @ClassName TestAtomicDemo
 *
 * 原子變數:在 java.util.concurrent.atomic 包下提供了一些原子變數,
 *  1. volatile 保證記憶體可見性
 *  2. CAS(Compare-And-Swap) 演算法保證資料變數的原子性
 *   CAS 演算法是硬體對于并發操作的支持
 *   CAS 包含了三個運算元:
 *   ①記憶體值  V
 *   ②預估值  A
 *   ③更新值  B
 *   當且僅當 V == A 時, V = B; 否則,不會執行任何操作,
 *
 * @Description:
 * @Author: WangWenpeng
 * @Version 1.0
 */
public class TestAtomicDemo {
    public static void main(String[] args) {
        AtomicDemo ad = new AtomicDemo();
        for (int i = 0; i < 10; i++) {
            new Thread(ad).start();
        }
    }
}

class AtomicDemo implements Runnable {

    private AtomicInteger serialNumber = new AtomicInteger(0);

    @Override
    public void run() {
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
        }
        System.out.println(Thread.currentThread().getName() + ":" + getSerialNumber());
    }

    public int getSerialNumber() {
        return serialNumber.getAndIncrement();
    }
}

AtomicInteger這個玩意是具有原子性的integer,用它替換后發現能保證執行緒安全

Connected to the target VM, address: '127.0.0.1:61323', transport: 'socket'
Thread-4:1
Thread-6:4
Thread-0:3
Thread-7:9
Thread-2:2
Thread-5:6
Thread-3:5
Thread-1:0
Thread-9:7
Thread-8:8
Disconnected from the target VM, address: '127.0.0.1:61323', transport: 'socket'

CAS 演算法

解決了原子性問題,解決了記憶體可見性的問題

CAS (Compare-And-Swap) 是一種硬體對并發的支持,針對多處理器操作而設計的處理器中的一種特殊指令,用于管理對共享資料的并發訪問,CAS 是一種無鎖的非阻塞演算法的實作,

CAS 包含了 3 個運算元:

  • 需要讀寫的記憶體值 V 進行比較的值 A 擬寫入的新值 B
  • 當且僅當 V 的值等于 A 時, CAS 通過原子方式用新值 B 來更新 V 的值,否則不會執行任何操作,
  • CAS比較失敗的時候不會放棄CPU,會反復執行,直到自己修改主記憶體的資料

模擬CAS演算法

/**
 * @ClassName TestCompareAndSwap
 * @Description: cas模擬  模擬帶鎖,底層不是帶synchronized
 * cas 每次修改之前,都會執行獲取比較操作
 * @Author: WangWenpeng
 * @Version 1.0
 */
public class TestCompareAndSwap {

    public static void main(String[] args) {
        final CompareAndSwap cas = new CompareAndSwap();
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    int expectValue = https://www.cnblogs.com/javastack/p/cas.getValue();
                    System.out.println(cas.compareAndSet(expectValue, (int) Math.random() * 101));
                }
            }).start();
        }
    }
}

class CompareAndSwap {
    public int value;

    //獲取記憶體值
    public synchronized int getValue() {
        return value;
    }

    //比較并交換
    public synchronized int compareAndSwap(int expectValue, int newV) {
        int oldV = value;
        //記憶體值和預估值一致 就替換
        if (oldV == expectValue) {
            this.value = newV;
        }
        return oldV;
    }

    //設定 呼叫比較并交換  看期望值和原來的值是否一致
    public synchronized boolean compareAndSet(int expectValue, int newV) {
        return expectValue == compareAndSwap(expectValue, newV);
    }
}

推薦一個 Spring Boot 基礎教程及實戰示例:
https://github.com/javastacks/javastack

原子變數

小工具包,支持在單個變數上解除鎖的執行緒安全編程,事實上,此包中的類可將 volatile 值、欄位和陣列元素的概念擴展到那些也提供原子條件更新操作的類,

類 AtomicBoolean、 AtomicInteger、 AtomicLong 和 AtomicReference 的實體各自提供對相應型別單個變數的訪問和更新,每個類也為該型別提供適當的實用工具方法,

AtomicIntegerArray、 AtomicLongArray 和 AtomicReferenceArray 類進一步擴展了原子操作,對這些型別的陣列提供了支持,這些類在為其陣列元素提供 volatile 訪問語意方面也引人注目,這對于普通陣列來說是不受支持的,

核心方法:boolean compareAndSet(expectedValue, updateValue)

java.util.concurrent.atomic 包下提供了一些原子操作的常用類:

AtomicBoolean 、
AtomicInteger 、
AtomicLong 、
AtomicReference
AtomicIntegerArray 、
AtomicLongArray
AtomicMarkableReference
AtomicReferenceArray
AtomicStampedReference3-ConcurrentHashMap

鎖分段機制ConcurrentHashMap

執行緒安全的hash表 每一段都是一個獨立的鎖

Java 5.0 在 java.util.concurrent 包中提供了多種并發容器類來改進同步容器的性能,

ConcurrentHashMap 同步容器類是Java 5 增加的一個執行緒安全的哈希表,對與多執行緒的操作,介于 HashMap 與 Hashtable 之間,內部采用“鎖分段”機制替代 Hashtable 的獨占鎖,進而提高性能,

此包還提供了設計用于多執行緒背景關系中的 Collection 實作:ConcurrentHashMap、 ConcurrentSkipListMap、 ConcurrentSkipListSet、CopyOnWriteArrayList 和 CopyOnWriteArraySet,當期望許多執行緒訪問一個給定 collection 時, ConcurrentHashMap 通常優于同步的 HashMap,ConcurrentSkipListMap 通常優于同步的 TreeMap,當期望的讀數和遍歷遠遠大于串列的更新數時, CopyOnWriteArrayList 優于同步的 ArrayList,

ConcurrentHashMap就是一個執行緒安全的hash表,我們知道HashMap是執行緒不安全的,Hash Table加了鎖,是執行緒安全的,因此它效率低,HashTable加鎖就是將整個hash表鎖起來,當有多個執行緒訪問時,同一時間只能有一個執行緒訪問,并行變成串行,因此效率低,所以JDK1.5后提供了ConcurrentHashMap,它采用了鎖分段機制,

1.8以后底層又換成了CAS,把鎖分段機制放棄了,CAS基本就達到了無鎖的境界,

另外,Java 8+ 系列面試題和答案全部整理好了,微信搜索?Java技術堆疊,在后臺發送:面試,?可以在線閱讀,

CopyOnWrite寫入并復制

package com.atguigu.juc;import java.util.*;import java.util.concurrent.CopyOnWriteArrayList;/* * CopyOnWriteArrayList/CopyOnWriteArraySet : “寫入并復制” * 注意:添加操作多時,效率低,因為每次添加時都會進行復制,開銷非常的大,并發迭代操作多時可以選擇, */public class TestCopyOnWriteArrayList {    public static void main(String[] args) {        HelloThread ht = new HelloThread();        for (int i = 0; i < 10; i++) {            new Thread(ht).start();        }    }}class HelloThread implements Runnable {    //private static List<String> list = Collections.synchronizedList(new ArrayList<String>()); //每次修改都會復制  添加操作多時  不適合選這個    private static CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();    static {        list.add("AA");        list.add("BB");        list.add("CC");    }    @Override    public void run() {        Iterator<String> it = list.iterator();        while (it.hasNext()) {            System.out.println(it.next());            list.add("AA");//邊迭代邊添加  會出現并發修改例外        }    }}

CountDownLatch 閉鎖

閉鎖,在完成某些運算時,只有其他所有執行緒的運算全部完成,當前運算才繼續執行,Java 5.0 在 java.util.concurrent 包中提供了多種并發容器類來改進同步容器的性能,

CountDownLatch 一個同步輔助類,在完成一組正在其他執行緒中執行的操作之前,它允許一個或多個執行緒一直等待,

閉鎖可以延遲執行緒的進度直到其到達終止狀態,閉鎖可以用來確保某些活動直到其他活動都完成才繼續執行:

  • 確保某個計算在其需要的所有資源都被初始化之后才繼續執行;
  • 確保某個服務在其依賴的所有其他服務都已經啟動之后才啟動;
  • 等待直到某個操作所有參與者都準備就緒再繼續執行,
import java.util.concurrent.CountDownLatch;/** * @ClassName TestCountDownLatch * @Description: 閉鎖操作 其他執行緒都執行完成后當前執行緒才能繼續執行 * @Author: WangWenpeng * @Version 1.0 */public class TestCountDownLatch {    public static void main(String[] args) throws InterruptedException {        final CountDownLatch latch = new CountDownLatch(5);        LatchDemo ld = new LatchDemo(latch);        //計算執行時間        long start = System.currentTimeMillis();        for (int i = 0; i < 5; i++) {            new Thread(ld).start();        }        //閉鎖 等待其他執行緒的執行        latch.await();        long end = System.currentTimeMillis();        System.out.println("執行時間===============================" + (end - start));    }}class LatchDemo implements Runnable {    private CountDownLatch latch;    public LatchDemo(CountDownLatch latch) {        this.latch = latch;    }    @Override    public void run() {        synchronized (this) {            try {                for (int i = 0; i < 1000; i++) {                    if (i % 2 == 0) {                        System.out.println(Thread.currentThread().getName() + "-------------" + i);                    }                }            } finally {                //執行緒執行完畢后  countdown 減一                latch.countDown();            }        }    }}

實作 Callable 介面

Java 5.0 在 java.util.concurrent 提供了一個新的創建執行執行緒的方式:Callable 介面

Callable 介面類似于 Runnable,兩者都是為那些其實體可能被另一個執行緒執行的類設計的,但是 Runnable 不會回傳結果,并且無法拋出經過檢查的例外,

Callable 需要依賴FutureTask , FutureTask 也可以用作閉鎖,

package com.atguigu.juc;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;/** * @ClassName TestCallable * @Description: * @Author: WangWenpeng * @Version 1.0 */public class TestCallable {    public static void main(String[] args) throws ExecutionException, InterruptedException {        CallableThreadDemo td = new CallableThreadDemo();        //futureTask 實作類的支持  用于接收運算結果        FutureTask<Integer> result = new FutureTask<>(td);        new Thread(result).start();//執行緒開始運行         Integer sum = result.get();//等待執行緒執行完成后 才能獲取到結果 也可以用于閉鎖操作作為等待項        System.out.println("總和" + sum);    }}/** * @Description 多了一個方法的回傳值  并且可以拋出例外 * @Author WangWenpeng * @Param */class CallableThreadDemo implements Callable<Integer> {    @Override    public Integer call() throws Exception {        int sum = 0;        for (int i = 0; i < 100; i++) {            sum += i;        }        return sum;    }}//class ThreadDemo implements Runnable{//    @Override//    public void run() {////    }//}

同步鎖顯示鎖 Lock

在 Java 5.0 之前,協調共享物件的訪問時可以使用的機制只有 synchronized 和 volatile ,Java 5.0 后增加了一些新的機制,但并不是一種替代內置鎖的方法,而是當內置鎖不適用時,作為一種可選擇的高級功能,

ReentrantLock 實作了 Lock 介面,并提供了與synchronized 相同的互斥性和記憶體可見性,但相較于synchronized 提供了更高的處理鎖的靈活性,

package com.atguigu.juc;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * @ClassName TestLock * @Description: 同步鎖 更靈活的方式 * lock上鎖  unlock釋放鎖 * @Author: WangWenpeng * @Version 1.0 */public class TestLock {    public static void main(String[] args) {        Ticket ticket = new Ticket();        new Thread(ticket, "1號視窗").start();        new Thread(ticket, "2號視窗").start();        new Thread(ticket, "3號視窗").start();    }}class Ticket implements Runnable {    private int ticket = 100;    private Lock lock = new ReentrantLock();    @Override    public void run() {        //這樣買票沒有問題        //while (ticket > 0) {        //    System.out.println(Thread.currentThread().getName() + "完成售票,余票為" + --ticket);        //}        //放大問題出現的記錄 出現了負票號        //while (true) {        //    if (ticket > 0) {        //        try {        //            Thread.sleep(200);        //            System.out.println(Thread.currentThread().getName() + "完成售票,余票為" + --ticket);        //        } catch (InterruptedException e) {        //            e.printStackTrace();        //        }        //    }        //}        //顯式加鎖和釋放鎖        while (true) {            lock.lock();            try {                if (ticket > 0) {                    try {                        Thread.sleep(200);                        System.out.println(Thread.currentThread().getName() + "完成售票,余票為" + --ticket);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            } finally {                lock.unlock();            }        }    }}

lock的等待喚醒機制

/**
 * @ClassName TestProductorAndConsumer
 * @Description: 生產者消費者模型
 * @Author: WangWenpeng
 * @Version 1.0
 */
public class TestProductorAndConsumer {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Productor productor = new Productor(clerk);
        Consumer consumer = new Consumer(clerk);

        //沒有等待喚醒機制的時候
        //生產者一直生產 不考錄消費者  可能造成資料丟失
        //消費者一直消費 不考慮生產者  可能造成重復消費
        new Thread(productor, "生產者a").start();
        new Thread(consumer, "消費者a").start();
    }
}

/**
 * 店員
 */
class Clerk {
    //庫存共享資料 存在安全問題
    private int product = 0;

    //進貨
    public synchronized void get() {
        if (product >= 10) {
            System.out.println("產品已滿,無法添加");
            try {
                this.wait();
            } catch (InterruptedException e) {
            }
        } else {
            this.notifyAll();
            System.out.println(Thread.currentThread().getName() + "店員進貨1個產品 庫存為" + ++product);
        }
    }

    //賣貨
    public synchronized void sale() {
        if (product <= 0) {
            System.out.println("產品缺貨,無法售賣");
            try {
                this.wait();
            } catch (InterruptedException e) {
            }
        } else {
            System.out.println(Thread.currentThread().getName() + "店員銷售1個產品 庫存為" + --product);
            this.notifyAll();
        }
    }
}

/**
 * 生產者
 */
class Productor implements Runnable {
    private Clerk clerk;
    public Productor(Clerk clerk) {
        this.clerk = clerk;
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.get();
        }
    }
}

/**
 * @Description 消費者
 * @Author WangWenpeng
 * @Date 6:45 2020/4/27
 * @Param
 */
class Consumer implements Runnable {
    private Clerk clerk;
    public Consumer(Clerk clerk) {
        this.clerk = clerk;
    }
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            clerk.sale();
        }
    }
}

lock出問題的情況

生產者等待,增加出問題的幾率 庫存空位改成1

 if (product >= 1) {
            System.out.println("產品已滿,無法添加");
--------------------------------------------------------------------------------------

@Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
            }
            clerk.get();
        }
    }

消費者等于0的時候, 兩個消費者同時生產,之后停住了,沒有其他執行緒去喚醒,導致停在生產者這里,

解決方法,去掉else,讓他能走喚醒方法,

//進貨public synchronized void get() {    if (product >= 1) {        System.out.println("產品已滿,無法添加");        try {            this.wait();        } catch (InterruptedException e) {        }    }    this.notifyAll();    System.out.println(Thread.currentThread().getName() + "店員進貨1個產品 庫存為" + ++product);}//賣貨public synchronized void sale() {    if (product <= 0) {        System.out.println("產品缺貨,無法售賣");        try {            this.wait();        } catch (InterruptedException e) {        }    }    System.out.println(Thread.currentThread().getName() + "店員銷售1個產品 庫存為" + --product);    this.notifyAll();}

讓執行緒能走到notifyall,可以避免停止在生產者這里,

虛假喚醒

增加到兩個消費者兩個生產者之后,如果現在沒有庫存,兩個消費者都停止在wait,然后出現生產者將庫存加一,喚醒所有消費者,這時候就出現了兩個消費者同時去消費一個庫存,導致庫存變成負數,這就是虛假喚醒,

在object類的wait方法中,虛假喚醒是可能的,因此這個wait方法應該總被使用在回圈中

解決方法

將代碼中的if換為while回圈執行

 //進貨    public synchronized void get() {        while (product >= 1) {  //wait使用在回圈中            System.out.println("產品已滿,無法添加");            try {                this.wait();            } catch (InterruptedException e) {            }        }        this.notifyAll();        System.out.println(Thread.currentThread().getName() + "店員進貨1個產品 庫存為" + ++product);    }    //賣貨    public synchronized void sale() {        while (product <= 0) {            System.out.println("產品缺貨,無法售賣");            try {                this.wait();            } catch (InterruptedException e) {            }        }        System.out.println(Thread.currentThread().getName() + "店員銷售1個產品 庫存為" + --product);        this.notifyAll();    }

控制執行緒通信Condition

Condition 介面描述了可能會與鎖有關聯的條件變數,這些變數在用法上與使用 Object.wait 訪問的隱式監視器類似,但提供了更強大的功能,需要特別指出的是,單個 Lock 可能與多個 Condition 物件關聯,為了避免兼容性問題, Condition 方法的名稱與對應的 Object 版本中的不同,

在 Condition 物件中,與 wait、 notify 和 notifyAll 方法對應的分別是await、 signal 和 signalAll,Condition 實體實質上被系結到一個鎖上,要為特定 Lock 實體獲得Condition 實體,請使用其 newCondition()方法,

/** * 店員 */class ClerkLock {    //庫存共享資料 存在安全問題    private int product = 0;    //使用lock,去掉synchronized   this.wait和lock就是兩把鎖,用lock統一    private Lock lock = new ReentrantLock();    private Condition condition = lock.newCondition();    //進貨    public void get() {        lock.lock();        try {            while (product >= 1) {                System.out.println("產品已滿,無法添加");                try {                    condition.await();                } catch (InterruptedException e) {                }            }            condition.signalAll();            System.out.println(Thread.currentThread().getName() + "店員進貨1個產品 庫存為" + ++product);        } finally {            lock.unlock();        }    }    //賣貨    public synchronized void sale() {        lock.lock();        try {            while (product <= 0) {                System.out.println("產品缺貨,無法售賣");                try {                    condition.await();                } catch (InterruptedException e) {                }            }            System.out.println(Thread.currentThread().getName() + "店員銷售1個產品 庫存為" + --product);            condition.signalAll();        } finally {        }    }}

這里店員的代碼全部處理為condition,用他的方法實作執行緒的通信,

執行緒按序交替執行緒按序交替

撰寫一個程式,開啟 3 個執行緒,這三個執行緒的 ID 分別為A、 B、 C,每個執行緒將自己的 ID 在螢屏上列印 10 遍,要求輸出的結果必須按順序顯示,

如:ABCABCABC…… 依次遞回9-ReadWriteLock 讀寫鎖讀-寫鎖 ReadWriteLock

package com.atguigu.juc;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * @ClassName TestABCAlternate * @Description: 執行緒交替列印 * @Author: WangWenpeng * @Version 1.0 */public class TestABCAlternate {    public static void main(String[] args) {        Alternate alternate = new Alternate();        new Thread(new Runnable() {            @Override            public void run() {                for (int i = 0; i <= 20; i++) {                    alternate.loopA(i);                }            }        }, "A").start();        new Thread(new Runnable() {            @Override            public void run() {                for (int i = 0; i <= 20; i++) {                    alternate.loopB(i);                }            }        }, "B").start();        new Thread(new Runnable() {            @Override            public void run() {                for (int i = 0; i <= 20; i++) {                    alternate.loopC(i);                }            }        }, "C").start();    }}class Alternate {    private int number = 1;//當前正在執行的執行緒號    private Lock lock = new ReentrantLock();    private Condition condition1 = lock.newCondition();    private Condition condition2 = lock.newCondition();    private Condition condition3 = lock.newCondition();    public void loopA(int totalLoop) {        lock.lock();        try {            //1.判斷1號執行緒            if (number != 1) {                condition1.await();            }            //2.開始列印            for (int i = 0; i < 5; i++) {                System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);            }            //3.喚醒執行緒2            number = 2;            condition2.signal();        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public void loopB(int totalLoop) {        lock.lock();        try {            //1.判斷1號執行緒            if (number != 2) {                condition2.await();            }            //2.開始列印            for (int i = 0; i < 5; i++) {                System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);            }            //3.喚醒執行緒2            number = 3;            condition3.signal();        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }    public void loopC(int totalLoop) {        lock.lock();        try {            //1.判斷1號執行緒            if (number != 3) {                condition3.await();            }            //2.開始列印            for (int i = 0; i < 5; i++) {                System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop);            }            //3.喚醒執行緒2            number = 1;            condition1.signal();        } catch (InterruptedException e) {            e.printStackTrace();        } finally {            lock.unlock();        }    }}

ReadWriteLock 讀寫鎖

ReadWriteLock 維護了一對相關的鎖,一個用于只讀操作,另一個用于寫入操作,只要沒有 writer,讀取鎖可以由多個 reader 執行緒同時保持,寫入鎖是獨占的,

ReadWriteLock 讀取操作通常不會改變共享資源,但執行寫入操作時,必須獨占方式來獲取鎖,對于讀取操作占多數的資料結構,ReadWriteLock 能提供比獨占鎖更高的并發性,而對于只讀的資料結構,其中包含的不變性可以完全不需要考慮加鎖操作,

讀鎖是多個執行緒可以一起,寫鎖是獨占的

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @ClassName ReadWriteLock
 * @Description: 讀寫鎖    讀和寫之間不互斥  寫和寫之間互斥
 * @Author: WangWenpeng
 * @Version 1.0
 */
public class TestReadWriteLock {
    public static void main(String[] args) {
        ReadWriteLockDemo demo = new ReadWriteLockDemo();

        new Thread(new Runnable() {
            @Override
            public void run() {
                demo.set((int) (Math.random() * 101));
            }
        }, "writeLock").start();

        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    demo.get();
                }
            }, "readLock-" + i).start();
        }
    }
}

class ReadWriteLockDemo {
    private int number = 0;
    private ReadWriteLock lock = new ReentrantReadWriteLock();

    //讀
    public void get() {
        lock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "讀:" + number);
        } finally {
            lock.readLock().unlock();
        }
    }

    //寫
    public void set(int number) {
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "寫:" + number);
            this.number = number;
        } finally {
            lock.writeLock().unlock();
        }
    }
}

執行緒八鎖

  • 一個物件里面如果有多個synchronized方法,某一個時刻內,只要一個執行緒去呼叫其中的一個synchronized方法了,其它的執行緒都只能等待,換句話說,某一個時刻內,只能有唯一一個執行緒去訪問這些synchronized方法
  • 鎖的是當前物件this,被鎖定后,其它的執行緒都不能進入到當前物件的其它的synchronized方法
  • 加個普通方法后發現和同步鎖無關
  • 換成兩個物件后,不是同一把鎖了,情況立刻變化,
  • 都換成靜態同步方法后,情況又變化
  • 所有的非靜態同步方法用的都是同一把鎖——實體物件本身,也就是說如果一個實體物件的非靜態同步方法獲取鎖后,該實體物件的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖后才能獲取鎖,可是別的實體物件的非靜態同步方法因為跟該實體物件的非靜態同步方法用的是不同的鎖,所以毋須等待該實體物件已獲取鎖的非靜態同步方法釋放鎖就可以獲取他們自己的鎖,
  • 所有的靜態同步方法用的也是同一把鎖——類物件本身,這兩把鎖是兩個不同的物件,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的,但是一旦一個靜態同步方法獲取鎖后,其他的靜態同步方法都必須等待該方法釋放鎖后才能獲取鎖,而不管是同一個實體物件的靜態同步方法之間,還是不同的實體物件的靜態同步方法之間,只要它們同一個類的實體物件!
/**
 * 1. 兩個普通同步方法,兩個執行緒,標準列印, 列印? //one  two
 */
public class TestThread8Monitor {
    public static void main(String[] args) {
        Number number = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
    number.getTwo();
            }
        }).start();
    }
}
class Number {
    public synchronized void getOne() {
        System.out.println("one");
    }
    public synchronized void getTwo() {
        System.out.println("two");
    }
}
/**
2. 新增 Thread.sleep() 給 getOne() ,列印? //one  two
 */
public class TestThread8Monitor {
    public static void main(String[] args) {
        Number number = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
    number.getTwo();
            }
        }).start();
    }
}
class Number {
    public synchronized void getOne() {
        try {
            Thread.sleep(3000);//讓one 睡3秒
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public synchronized void getTwo() {
        System.out.println("two");
    }
}
/*
 *3. 新增普通方法 getThree() , 列印? //three  one   two
 */
public class TestThread8Monitor {
    public static void main(String[] args) {
        Number number = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
    number.getTwo();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
    number.getThree();
            }
        }).start();
    }
}
class Number {
    public synchronized void getOne() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public synchronized void getTwo() {
        System.out.println("two");
    }
    //普通方法
    public void getThree(){
     System.out.println("three");
    }
}
/*
 * 4. 兩個普通同步方法,兩個 Number 物件,列印?  //two  one
 */
public class TestThread8Monitor {
    public static void main(String[] args) {
        Number number = new Number();
        Number number2 = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number2.getTwo();
            }
        }).start();
    }
}

class Number {
    public synchronized void getOne() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public synchronized void getTwo() {
        System.out.println("two");
    }
}
/*
 * 5. 修改 getOne() 為靜態同步方法,列印?  //two   one
 */
public class TestThread8Monitor {
    public static void main(String[] args) {
        Number number = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }//這樣其實不能通過類的實體訪問靜態,為演示這個問題
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getTwo();
            }
        }).start();
    }
}
class Number {
    //靜態同步方法
    public static synchronized void getOne() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public synchronized void getTwo() {
        System.out.println("two");
    }
}
 /*
  * 6. 修改兩個方法均為靜態同步方法,一個 Number 物件?  //one   two
 */
public class TestThread8Monitor {

    public static void main(String[] args) {
        Number number = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }//這樣其實不能通過類的實體訪問靜態,為演示這個問題
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getTwo();
            }
        }).start();
    }
}
class Number {
    public static synchronized void getOne() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public static synchronized void getTwo() {
        System.out.println("two");
    }
}
 /*
 * 7. 一個靜態同步方法,一個非靜態同步方法,兩個 Number 物件?  //two  one
 */
public class TestThread8Monitor {

    public static void main(String[] args) {
        Number number = new Number();
        Number number2 = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }//這樣其實不能通過類的實體訪問靜態,為演示這個問題
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number2.getTwo();
            }
        }).start();
    }
}

class Number {
    public static synchronized void getOne() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public  synchronized void getTwo() {
        System.out.println("two");
    }
}
 /*
 * 8. 兩個靜態同步方法,兩個 Number 物件?   //one  two
 */
public class TestThread8Monitor {

    public static void main(String[] args) {
        Number number = new Number();
        Number number2 = new Number();
        new Thread(new Runnable() {
            @Override
            public void run() {
                number.getOne();
            }//這樣其實不能通過類的實體訪問靜態,為演示這個問題
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                number2.getTwo();
            }
        }).start();
    }
}
class Number {
    public static synchronized void getOne() {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
        }
        System.out.println("one");
    }
    public static synchronized void getTwo() {
        System.out.println("two");
    }
}

執行緒八鎖的關鍵:

  • 非靜態方法的鎖默認為 this, 靜態方法的鎖為 對應的 Class 實體
  • 某一個時刻內,只能有一個執行緒持有鎖,無論幾個方法,

執行緒池

第四種獲取執行緒的方法:執行緒池,一個 ExecutorService,它使用可能的幾個池執行緒之一執行每個提交的任務,通常使用 Executors 工廠方法配置,

執行緒池可以解決兩個不同問題:由于減少了每個任務呼叫的開銷,它們通常可以在執行大量異步任務時提供增強的性能,并且還可以提供系結和管理資源(包括執行任務集時使用的執行緒)的方法,每個 ThreadPoolExecutor 還維護著一些基本的統計資料,如完成的任務數,

為了便于跨大量背景關系使用,此類提供了很多可調整的引數和擴展鉤子 (hook),但是,強烈建議程式員使用較為方便的 Executors 工廠方法 :

  • Executors.newCachedThreadPool()(無界執行緒池,可以進行自動執行緒回收)
  • Executors.newFixedThreadPool(int)(固定大小執行緒池)
  • Executors.newSingleThreadExecutor()(單個后臺執行緒)它們均為大多數使用場景預定義了設定,
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;

/**
 * @Description 一、執行緒池:提供了一個執行緒佇列,佇列中保存著所有等待狀態的執行緒,避免了創建與銷毀額外開銷,提高了回應的速度,
 * 二、執行緒池的體系結構:
 * java.util.concurrent.Executor : 負責執行緒的使用與調度的根介面
 *   |--**ExecutorService 子介面: 執行緒池的主要介面
 *    |--ThreadPoolExecutor 執行緒池的實作類
 *    |--ScheduledExecutorService 子介面:負責執行緒的調度
 *     |--ScheduledThreadPoolExecutor :繼承 ThreadPoolExecutor, 實作 ScheduledExecutorService
 * 三、工具類 : Executors
 * ExecutorService newFixedThreadPool() : 創建固定大小的執行緒池
 * ExecutorService newCachedThreadPool() : 快取執行緒池,執行緒池的數量不固定,可以根據需求自動的更改數量,
 * ExecutorService newSingleThreadExecutor() : 創建單個執行緒池,執行緒池中只有一個執行緒
 * ScheduledExecutorService newScheduledThreadPool() : 創建固定大小的執行緒,可以延遲或定時的執行任務,
 * @Author WangWenpeng
 * @Param
 */
public class TestThreadPool {

    public static void main(String[] args) throws Exception {
        //1. 創建執行緒池
        ExecutorService pool = Executors.newFixedThreadPool(5);

        //submit Callable方法
        List<Future<Integer>> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Future<Integer> future = pool.submit(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int sum = 0;
                    for (int i = 0; i <= 100; i++) {
                        sum += i;
                    }
                    return sum;
                }
            });
            list.add(future);
        }
        pool.shutdown();
        for (Future<Integer> future : list) {
            System.out.println(future.get());
        }

        //submit Runnable方法
        ThreadPoolDemo tpd = new ThreadPoolDemo();
        //2. 為執行緒池中的執行緒分配任務
        for (int i = 0; i < 10; i++) {
            pool.submit(tpd);
        }
        //3. 關閉執行緒池
        pool.shutdown();
    }
}

class ThreadPoolDemo implements Runnable {
    private int i = 0;

    @Override
    public void run() {
        while (i <= 100) {
            System.out.println(Thread.currentThread().getName() + " : " + i++);
        }
    }
}

執行緒調度ScheduledExecutorService

一個 ExecutorService,可安排在給定的延遲后運行或定期執行的命令,

public static void main(String[] args) throws Exception {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);
        for (int i = 0; i < 5; i++) {
            Future<Integer> result = pool.schedule(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    int num = new Random().nextInt(100);//生成亂數
                    System.out.println(Thread.currentThread().getName() + " : " + num);
                    return num;
                }
            }, 1, TimeUnit.SECONDS);
            System.out.println(result.get());
        }
        pool.shutdown();
    }

ForkJoinPool 分支/合并框架

就是在必要的情況下,將一個大任務,進行拆分(fork)成若干個小任務(拆到不可再拆時),再將一個個的小任務運算的結果進行 join 匯總,

JoinFork/Join 框架與執行緒池的區別

采用 “作業竊取”模式(work-stealing):

  • 當執行新的任務時它可以將其拆分分成更小的任務執行,并將小任務加到執行緒佇列中,然后再從一個隨機執行緒的佇列中偷一個并把它放在自己的佇列中,
  • 相對于一般的執行緒池實作, fork/join框架的優勢體現在對其中包含的任務的處理方式上,在一般的執行緒池中, 如果一個執行緒正在執行的任務由于某些原因無法繼續運行, 那么該執行緒會處于等待狀態,而在fork/join框架實作中,如果某個子問題由于等待另外一個子問題的完成而無法繼續運行,那么處理該子問題的執行緒會主動尋找其他尚未運行的子問題來執行.這種方式減少了執行緒的等待時間, 提高了性能,
public class TestForkJoinPool {
    public static void main(String[] args) {
        Instant start = Instant.now();
        ForkJoinPool pool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinSumCalculate(0L, 50000000000L);
        Long sum = pool.invoke(task);
        System.out.println(sum);
        Instant end = Instant.now();
        System.out.println("耗費時間為:" + Duration.between(start, end).toMillis());//166-1996-10590
    }
}

class ForkJoinSumCalculate extends RecursiveTask<Long> {
    private static final long serialVersionUID = -259195479995561737L;

    private long start;
    private long end;

    private static final long THURSHOLD = 10000L;  //臨界值

    public ForkJoinSumCalculate(long start, long end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Long compute() {
        long length = end - start;
        if (length <= THURSHOLD) {
            long sum = 0L;
            for (long i = start; i <= end; i++) {
                sum += i;
            }
            return sum;
        } else {
            long middle = (start + end) / 2;
            ForkJoinSumCalculate left = new ForkJoinSumCalculate(start, middle);
            left.fork(); //進行拆分,同時壓入執行緒佇列
            ForkJoinSumCalculate right = new ForkJoinSumCalculate(middle + 1, end);
            right.fork();
            return left.join() + right.join();
        }
    }
}

近期熱文推薦:

1.1,000+ 道 Java面試題及答案整理(2021最新版)

2.別在再滿屏的 if/ else 了,試試策略模式,真香!!

3.臥槽!Java 中的 xx ≠ null 是什么新語法?

4.Spring Boot 2.5 重磅發布,黑暗模式太炸了!

5.《Java開發手冊(嵩山版)》最新發布,速速下載!

覺得不錯,別忘了隨手點贊+轉發哦!

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

標籤:Java

上一篇:使用決議器選擇器從動態Web表中抓取資料

下一篇:你如何繼續要求整數并將它們添加到回圈中?

標籤雲
其他(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)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more