主頁 >  其他 > JUC并發編程小總結

JUC并發編程小總結

2021-04-23 17:05:27 其他

JUC是Java編發編程中使用的工具類,全稱為java.util.concurrent,近期在大廠面試中屢屢被問到關于JUC的相關知識點問題,其重要性不言而喻,學好用好JUC可以說是每一個Java程式開發作業者不能不研究和不能不解決的問題,因此,在自己屢屢碰壁后決定對JUC的相關知識點進行一次系統的梳理,并通過博客分享的方式進行一個大概的總結,所記如下,

一、與JUC相關概念回顧

JUC是Java并發編程中使用的核心工具類,主要包括:
(1)鎖機制類Locks:Lock, Condition, ReadWriteLock等,
(2)原子操作類:Atomic:AtomicInteger, AtomicLong等,
(2)并發集合類:CopOnWriteArrayList, ConcurrentHashMap等,
(3)信號量三組工具類:CountDownLatch, CyclicBarrier, Semaphore,
(4)執行緒池相關類:Feture, Callable, Executor等,
在說JUC工具類之前我們首先回顧一下在并發編程中經常會混淆的幾個概念,執行緒和行程的區別是什么?并發和并行的區別是什么?阻塞和非阻塞的區別是什么?同步和異步的區別是什么?我覺得這幾個概念可以用生活中的例子進行類比,記起來更加方便,
行程: 打開QQ,開了一個行程;打開微信,開了一個行程;打開王者榮耀,又開了一個行程,
執行緒: 使用微信時,一邊與A好友進行文字聊天是一個執行緒,同時與B好友進行語音通話是一個執行緒,同時正在給C好友傳輸檔案又是一個執行緒,
(單行程單執行緒:一個人在一張桌子吃飯;單行程多執行緒:多個人在同一張桌子上一起吃飯)
并發: 兩個佇列交替使用一臺咖啡機,
并行: 兩個佇列使用兩臺咖啡機,
Erlang 之父 Joe Armstrong 用一張5歲小孩都能看懂的圖解釋了并發與并行的區別,如下:
在這里插入圖片描述
阻塞: 排隊打飯,什么都不做等著輪到自己打飯為止,
非阻塞: 奶茶店買奶茶,當奶茶還做好前,先找個位置打兩盤王者,等奶茶做好后再去拿,
同步: 去肯德基買全家桶,等全家桶做好后通知我,我自己去前臺拿,
異步: 去酒店吃飯,等菜做好后端到我的桌面上,
(資料就緒后,需要自己去讀就是同步,資料就緒直接讀好再回呼給程式就是異步,可以理解為菜做好后如果是自己去拿就是同步,一手包辦送到我桌面就是異步)

二、鎖機制類

1、synchronized同步鎖回顧

在講JUC中的鎖機制前先回顧一下synchronized同步鎖,
使用口訣:執行緒操作資源類
題目:三個售票員賣出30張票,使用synchronized的代碼如下:

//資源類
public class Ticket0 {
    private int count = 30;
    public synchronized void sale() {
        if(count > 0) {
            System.out.println(Thread.currentThread().getName() + "賣出第" + (count--) +"張票,還剩" + count + "張票");
        }
    }
}
//執行緒
public class TicketDemo02 {
    public static void main(String[] args) {
        Ticket0 ticket = new Ticket0();
        //售票員1
        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        }, "AAA").start();
		//售票員2
        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        }, "BBB").start();
		//售票員3
        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        }, "CCC").start();
    }
}

2、使用JUC的Lock

同樣的解決三個售票員賣出30張票的問題,使用Lock的代碼如下:

//資源類
public class Ticket {
    private int count = 30;
    private ReentrantLock lock = new ReentrantLock();
    public void sale() {
        lock.lock();
        try {
            if(count > 0) {
                System.out.println(Thread.currentThread().getName() + "賣出第" + (count--) +"張票,還剩" + count + "張票");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
//執行緒
public class TicketDemo02 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        }, "AAA").start();

        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        }, "BBB").start();

        new Thread(()-> {
            for (int i = 0; i < 40; i++) {
                ticket.sale();
            }
        }, "CCC").start();
    }
}

單從賣票這個問題上來看,synchronized和Lock的效果一樣,那么他們的區別在哪里呢?
(1)synchronized是一個關鍵字,在jvm層面;而Lock是一個Java類,在API層面,
(2)synchronized無法判斷是否已獲取鎖的狀態,而Lock可以通過tryLock方法判斷獲取鎖的狀態
(3)synchronized自動釋放鎖,Lock需要手動釋放鎖,一般會在finally中使用unlock方法釋放,否則容易導致死鎖,
(4)synchronized是同步阻塞,而Lock是同步非阻塞
(5)synchronized適合代碼少量同步的同步問題,Lock適合代碼大量同步的同步問題,

3、執行緒間的通信

執行緒間的通信間的通信比較典型的就是生產者消費者模式,我們可以把現場間的通信歸納為以下口訣:
(1)執行緒操作資源類
(2)判斷、干活、通知
(3)多執行緒互動中,必須要防止虛假喚醒(在資源類做判斷等待時,必須使用while,禁止使用if)
題目:兩個執行緒,可以操作初始值為0的一個變數實作一個執行緒對該變數加1,一個執行緒對該變數減1實作交替10輪,變數最后初始值為0,
先回顧以往使用synchronized+wait/notify的實作方式,代碼如下:

//資源類
public class AirConditionor {
    private int num = 0;
    public synchronized void increament() throws InterruptedException {
    	//這里必須使用while,否則會產生虛假喚醒現象
        while (num != 0) {
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName() + ":" + num);
        this.notifyAll();
    }
    public synchronized void decreament() throws InterruptedException {
    	//這里必須使用while,否則會產生虛假喚醒現象
        while (num == 0) {
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName() + ":" + num);
        this.notifyAll();
    }
}
//執行緒
public class ThreadWaitNotifyDemo02 {
    public static void main(String[] args) {
        AirConditionor airConditionor = new AirConditionor();
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    airConditionor.increament();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AAA").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    airConditionor.decreament();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BBB").start();


        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    airConditionor.increament();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CCC").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    airConditionor.decreament();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "DDD").start();

    }
}

使用Lock進行執行緒間的通信,通過Condition介面來實作,相同的問題,代碼如下:

//資源類
public class AirConditionor2 {
    private int num = 0;
    private ReentrantLock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    public void  increament() {
        lock.lock();
        try {
            while (num != 0) {
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName() + ":" + num);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
    public void  decreament()   {
        lock.lock();
        try {
            while (num == 0) {
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName() + ":" + num);
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }
}
//執行緒
public class ThreadWaitNotifyDemo03 {
    public static void main(String[] args) {
        AirConditionor airConditionor2 = new AirConditionor();
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    airConditionor2.increament();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "AAA").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    airConditionor2.decreament();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "BBB").start();
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    airConditionor2.increament();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "CCC").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    airConditionor2.decreament();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "DDD").start();
    }
}

直接這樣看以上問題的例子好像沒什么區別,那么synchronized+wait/notify和lock+await/signal的主要區別在哪里呢?主要體現在執行緒間定制化呼叫通信中,可以理解為前者是使用炮彈轟炸,后者是使用導彈精準打擊,題目例子如下:三個執行緒啟動,AAA列印5次,BBB列印10次,CCC列印15次,如此回傳列印10輪,
可以通過Lock實作Condition介面創建多個,每一個功能對應一個Condition,通過相應的等待喚醒方法來實作精準控制,代碼如下:

//資源類
public class MyThread {
    /**
     * 0代表列印5次,1代表列印10次,2代表列印15次
     */
    private int num = 0;
    Lock lock = new ReentrantLock();
    Condition condition0 = lock.newCondition();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    public void print5()  {
        lock.lock();
        try {
            while (num != 0) {
                condition0.await();
            }
            for (int i = 1; i <= 5; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
            num = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void print10()  {
        lock.lock();
        try {
            while (num != 1) {
                condition1.await();
            }
            for (int i = 1; i <= 10; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
            num = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void print15()  {
        lock.lock();
        try {
            while (num != 2) {
                condition2.await();
            }
            for (int i = 1; i <= 15; i++) {
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
            num = 0;
            condition0.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
//執行緒
public class ThreadOrderAccessDemo04 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
               myThread.print5();
            }
        }, "AAA").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                myThread.print10();
            }
        }, "BBB").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                myThread.print15();
            }
        }, "CCC").start();
    }
}

4、鎖的8個問題

對于資源類方法的靜態和靜態,加鎖和不加鎖,衍生出令人混淆的8個關于鎖的問題:
以一個含有sendEmail()和sendMs()及sayHello()的方法的Phone類為例子,問題如下:
(1)標準訪問,先列印郵件

public class Phone {
    public  synchronized void sendEmail() throws Exception {
        System.out.println("*****sendEmail");
    }
    public  synchronized void sendMs() throws Exception {
        System.out.println("*****sendMs");
    }
}
public class Lock8 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(() -> {
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();
		Thread.sleep(100); //目的是先使執行緒A先訪問到資源
        new Thread(() -> {
            try {
                phone.sendMs();
                //phone.sayHello();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

(2)郵件設定暫停4秒方法,先列印郵件

public class Phone {
    public  synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("*****sendEmail");
    }
    public  synchronized void sendMs() throws Exception {
        System.out.println("*****sendMs");
    }
}
public class Lock8 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(() -> {
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();
		Thread.sleep(100); //目的是先使執行緒A先訪問到資源
        new Thread(() -> {
            try {
                phone.sendMs();
                //phone.sayHello();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

(3)新增sayHello方法,先列印sayHello

public class Phone {
    public  synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("*****sendEmail");
    }
    public  synchronized void sendMs() throws Exception {
        System.out.println("*****sendMs");
    }
    public void sayHello() throws InterruptedException {
        System.out.println("*****sayHello");
    }
}
public class Lock8 {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(() -> {
            try {
                phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        new Thread(() -> {
            try {
//                phone.sendMs();
                phone.sayHello();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

(4)兩部手機,先列印短信

public class Phone {
    public  synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("*****sendEmail");
    }

    public  synchronized void sendMs() throws Exception {
        System.out.println("*****sendMs");
    }

    public void sayHello() throws InterruptedException {
        System.out.println("*****sayHello");
    }
}
public class Lock8 {
    public static void main(String[] args) {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            try {
                phone1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        new Thread(() -> {
            try {
                phone2.sendMs();
                //phone2.sayHello();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

(5)兩個靜態同步方法,同一部手機,先列印郵件

public class Phone {
    public static synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("*****sendEmail");
    }

    public static synchronized void sendMs() throws Exception {
        System.out.println("*****sendMs");
    }

    public void sayHello() throws InterruptedException {
        System.out.println("*****sayHello");
    }
}
public class Lock8 {
    public static void main(String[] args) {
        //Phone phone1 = new Phone();
        //Phone phone2 = new Phone();
        new Thread(() -> {
            try {
                Phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        new Thread(() -> {
            try {
                Phone.sendMs();
                //phone2.sayHello();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

(6)兩個靜態同步方法,同兩部手機,先列印郵件,鎖的同一個位元組碼物件

public class Phone {
    public static synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("*****sendEmail");
    }
    public static synchronized void sendMs() throws Exception {
        System.out.println("*****sendMs");
    }
    public void sayHello() throws InterruptedException {
        System.out.println("*****sayHello");
    }
}
public class Lock8 {
    public static void main(String[] args) {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            try {
                Phone.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        new Thread(() -> {
            try {
                Phone.sendMs();
                //phone2.sayHello();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

(7)一個靜態同步方法,一個普通同步方法,同一部手機,先列印短信

public class Phone {
    public static synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("*****sendEmail");
    }
    public  synchronized void sendMs() throws Exception {
        System.out.println("*****sendMs");
    }
    public void sayHello() throws InterruptedException {
        System.out.println("*****sayHello");
    }
}
public class Lock8 {
    public static void main(String[] args) {
        Phone phone1 = new Phone();
        //Phone phone2 = new Phone();
        new Thread(() -> {
            try {
                phone1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();
        new Thread(() -> {
            try {
                //phone2.sendMs();
                phone1.sendMs();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

(8)一個靜態同步方法,一個普通同步方法,同二部手機,先列印短信

public class Phone {
    public static synchronized void sendEmail() throws Exception {
        TimeUnit.SECONDS.sleep(4);
        System.out.println("*****sendEmail");
    }
    public  synchronized void sendMs() throws Exception {
        System.out.println("*****sendMs");
    }

    public void sayHello() throws InterruptedException {
        System.out.println("*****sayHello");
    }
}
public class Lock8 {
    public static void main(String[] args) {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            try {
                phone1.sendEmail();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "A").start();

        new Thread(() -> {
            try {
                //phone2.sendMs();
                phone2.sendMs();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "B").start();
    }
}

總結分析:
(1)普通同步方法,鎖的是當前實體物件;靜態同步方法鎖的是當前類的Class物件,
(2)普通同步方法、靜態同步方法及普通方法相互之前不會有競態條件,

5、ReadWriteLock讀寫鎖

ReadWriteLock的原理可以總結為:寫的時候獨占,讀的時候共享,讀-讀能共存、讀-寫不能共存、寫-寫不能共存,代碼例子如下:

public class MyCache {
    private volatile Map<String, Object> map = new HashMap<>();
    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    public void put(String key, Object value) {
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "---寫入資料" + value);
            TimeUnit.SECONDS.sleep(3);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "---寫入完成");
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            readWriteLock.writeLock().unlock();
        }
    }
    public void get(String key) {
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "讀取資料");
            //TimeUnit.SECONDS.sleep(3);
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName() + "讀取完成" + result);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
}
========
輸出:
5---寫入資料5
5---寫入完成
4---寫入資料4
4---寫入完成
1---寫入資料1
1---寫入完成
2---寫入資料2
2---寫入完成
3---寫入資料3
3---寫入完成
1讀取資料
1讀取完成1
2讀取資料
3讀取資料
4讀取資料
2讀取完成2
4讀取完成4
3讀取完成3
5讀取資料
5讀取完成5

三、并發集合類

在多執行緒環境下,使用ArrayList、HasSet及HashMap集合類時,會產生執行緒安全問題,報java.util.ConcurrentModificationException例外,那么為什么會導致產生這樣的例外呢?
我們可以類比成一個上課簽到的場景,簽到表相當于集合容器,多個學生相當于多個執行緒,由于缺乏紀律規則提示,大家都爭搶著往這個簽到表中簽名,導致一個學生還沒簽到完,就被另個學生把簽到表搶去,導致產生了混亂,甚至產生踩踏事件,

1、ArrayList執行緒不安全問題解決方案

(1)使用uitil包下的Vector類
(2)Collections.synchronizedList(new ArrayList<>())
(3)java.util.concurrent 包下的new CopyOnWriteArrayList<>()

public class NoSafeDemo {
    public static void main(String[] args) {
        List<String> list1 = new Vector<>();
        List<String> list2 = Collections.synchronizedList(new ArrayList<>());
        List<String> list3 = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 50; i++) {
            new Thread(()-> {
                list1.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list1);
                list2.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list2);
                list3.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list3);
            }, String.valueOf(i)).start();
        }
    }
}

方法(1)和(2)底層都是使用了synchronized實作,而CopyOnWrite容器底層使用了寫時復制,讀寫分離的思想實作,
在這里插入圖片描述
核心原始碼如下:

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

實作原理:當往一個容器中添加元素時,不直接往Object[]添加,而是現將當前元素進行Copy并把陣列的長度+1,復制出新的陣列Object[] newElements,然后向新的容器添加元素,最后把原容器的參考指向新的容器 setArray(newElements);這種設計思想的好處是并發讀的時候不需要鎖,因為當前容器不會添加任何元素,提高了效率,

2、HashSet執行緒不安全問題解決方案

HashSet的執行緒不安全問題解決方案和ArrayList相似,
(1)使用Collections.synchronizedSet(new HashSet<>())
(2)java.util.concurrent 包下的CopyOnWriteArraySet

3、HashMap執行緒不安全問題解決方案

(1)使用Collections.synchronizedMap(new HashMap<>())
(2)java.util.concurrent 包下的ConcurrentHashMap

public class NoSafeDemo03 {
    public static void main(String[] args) {
        ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
        for (int i = 0; i < 30; i++) {
            new Thread(()-> {
                map.put(UUID.randomUUID().toString().substring(0, 7), "aaa");
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

原理分析:ConcurrentHashMap使用分段鎖的思想,容器中有多把鎖,每一把鎖鎖一段資料,這樣在多執行緒訪問時,不同段的資料不存在鎖的競爭,這樣就可以有效的提高并發效率,

4、BlockingQueue阻塞佇列

在多執行緒的阻塞,是在某種情況下會掛起執行緒,一旦條件滿足,掛起的執行緒又會自動喚醒,使用BlockingQueue的好處是我們不需要關心什么時候需要阻塞執行緒,什么時候需要喚醒執行緒,BlockingQueue都給一手包辦,
(1)當佇列是空的,從佇列中獲取元素的操作將會被阻塞
(2)當佇列是滿的,從佇列中添加元素的操作將會被阻塞

public class BlockingQueueDemo  {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
		//由于添加元素超過佇列大小,會產生阻塞
        blockingQueue.put("aaa");
        blockingQueue.put("aaa");
        blockingQueue.put("aaa");
        blockingQueue.put("aaa");
        
    }
}

四、執行緒池

1、執行緒的獲取方法及Callable介面

獲得執行緒的方法一般情況下有一下三種:
(1)繼承Thread類并重寫run方法,
(2)實作Runnable介面并重寫run方法,
(3)實作java.util.concurrent包下的Callable介面并重寫call方法,
使用Callable創建執行緒的方法代碼如下:

//資源類
public class MyThread implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        //更加細粒度化的精確控制
        System.out.println(Thread.currentThread().getName() +"come in here");
        TimeUnit.SECONDS.sleep(4);
        return 1024;
    }
}
//執行緒
public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //MyThread myThread = new MyThread();
        FutureTask futureTask = new FutureTask(new MyThread());
        new Thread(futureTask, "AAA").start();
        new Thread(futureTask, "BBB").start();

        System.out.println(Thread.currentThread().getName() + "計算完成!");
        System.out.println(futureTask.get());
    }
}

因為Thread(Runnable target) 的輸入變數為Runnable , 實作Callable介面的MyThread 不能輸入到Thread中,那么FutureTask(Callable callable) 相當于一個中間人,目的是通過FutureTask使Runnable和Callable產生聯系,
優點: 使用FutureTask的好處是對于主執行緒來說是同步非阻塞的,例子:老師上著課,口渴了,去買水不合適,講課執行緒繼續,我可以單起個執行緒找班長幫忙買水,水買回來了放桌上,我需要的時候再去get,(注意:get方法而獲取結果只有在計算完成時獲取,否則會一直阻塞直到任務轉入完成狀態)
Callable介面和Runnable介面的區別?
(1)前者有回傳值,后者沒有回傳值
(2)前者可以拋出例外,后者不可拋出例外
(3)前者的實作方法為call(),后者的實作方法為run()

2、為什么時候執行緒池?

執行緒池做的作業主要是控制運行的執行緒數量,處理程序中將任務放進佇列,然后在執行緒創建后啟動這些任務,如果執行緒數量超過了核心執行緒的最大數量,超出數量的執行緒排隊等候,等其他執行緒執行完畢,再從佇列中取出任務來執行,
主要特點為: 執行緒的復用;控制最大的并發數;管理執行緒
在這里插入圖片描述

3、使用Excutors創建執行緒池的3種方式

(1)newFixedThreadPool(int nThreads) 方法創建,創建的執行緒池corePoolSize和maxinumPoolSize值是相等的,它使用的是LinkedBlockingQueue

//創建固定執行緒池
ExecutorService threadPool = newFixedThreadPool(5);

(2)newSingleThreadExecutor()方法創建,創建的執行緒池corePoolSize和maximumPoolSize值都是1,它使用的是LinkedBlockingQueue

//一個池子一個作業執行緒
ExecutorService threadPool = newSingleThreadExecutor();

(3)newCachedThreadPool()方法創建,創建的執行緒池corePoolSize為0,maximumPoolSize值都是Integer.MAX_VALUE,使用的SynchronousQueue,也就是說來了任務就創建執行緒運行,當執行緒空閑超過60秒,就銷毀執行緒,

//一池N執行緒,有N個受理視窗,創建多少個由實際情況而定,擴拓展的
ExecutorService threadPool2 = newCachedThreadPool();

以上三種創建執行緒池的底層都是通過ThreadPoolExecutor創建,底層到嗎如下:

public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>(),
                                  threadFactory);
}
public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) {
 return new FinalizableDelegatedExecutorService
     (new ThreadPoolExecutor(1, 1,
                             0L, TimeUnit.MILLISECONDS,
                             new LinkedBlockingQueue<Runnable>(),
                             threadFactory));
}
public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                 60L, TimeUnit.SECONDS,
                                 new SynchronousQueue<Runnable>(),
                                 threadFactory);
}

阿里巴巴開發手冊關于執行緒池不允許使用Executors創建,而是通過ThreadPoolExecutor的方式創建,這樣的處理方式能讓撰寫代碼的工程師更加明確執行緒池的運行規則,避免資源耗盡的風險,弊端如下:
(1)使用FixedThreadPool和SingleThreadPool允許請求的佇列長度為Integer.MAX_VALUE,可能會堆積大量請求,導致OOM,
(2)CachedThreadPool允許創建最大的執行緒數為Integer.MAX_VALUE,創建大量的執行緒,導致OOM

4、執行緒池ThreadPoolExecutor的7大引數

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {......}

(1)corePoolSize:執行緒池中的常駐核心執行緒數
(2)maximumPoolSize:執行緒池中能容納的最大執行緒數
(3)keepAliveTime:多余空閑執行緒的存貨時間,當前執行緒數量超過corePoolSize且空閑時間達到keepAlive時,多余執行緒會被自動銷毀,使執行緒池中的執行緒數量保存為corePoolSize
(4)unit:keepAliveTime的時間單位
(5)workQueue:任務佇列,被提交但是未被執行的任務
(6)threadFactory:生成執行緒池中作業執行緒的執行緒工廠,用于創建執行緒
(7)handler:拒接策略,當佇列滿了的時候,并且作業執行緒大于或等于執行緒池的最大執行緒數時,如何拒絕請求執行的runnable的策略

5、執行緒池的底層作業原理

在這里插入圖片描述
(1)創建了執行緒后,開始等待請求
(2)當呼叫一個Excuror()方法添加一個請求任務時,執行緒池會作出以下判斷:

  • 如果正在運行的執行緒數小于corePoolSize,那么馬上創建這個執行緒運行這個任務
  • 如果正在運行的大于corePoolSize,那么將這個任務放入佇列
  • 如果佇列滿了,但是運行執行緒數小于maximumPoolSize,那么創建非核心執行緒來運行這個任務
  • 如果佇列滿了且運行的執行緒數大于maximumPoolSize,那么執行緒會啟動拒絕策略來執行

(3)當一個執行緒完成任務時,他會從一個佇列中取下一個任務執行,

(4)當一個執行緒無事可做,且超過keepAliveTime時,執行緒會判斷:

  • 如果當前運行的執行緒數大于corePoolSize,那么這個執行緒將會被停掉
  • 所以執行緒池的所有任務完成后,它最侄訓收縮到corePoolSize的大小

可以把整個程序看成是銀行網點辦理業務,假設一共有5個視窗(maximumPoolSize),候客區有3個座位(workQueue的大小),今天開放的只有2個視窗(corePoolSize),存在以下情況:
(1)當來的客戶不大于2個人時,直接辦理
(2)當來的客戶大于2個人不大于5個人時,剩余的顧客在候客區等待
(3)當來的客戶大于5個人不大于8個人時,擴展不大于3的視窗數給候客區的客戶辦理業務
(4)當來的客戶大于8個人時,銀行坐不下了,告訴坐不下的客戶去其他地方或者遲點再來

6、ThreadPoolExecutor的拒絕策略

(1)AbortPolicy(默認):直接拋出RejectedExecutionExeception

public class MyThreadPoolDemo02 {
    public static void main(String[] args) throws InterruptedException {
        //CPU最大核數->CPU密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
        //IO密集型,
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2,3,2L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );
        for (int i = 0; i < 5; i++) {
            //TimeUnit.SECONDS.sleep(1);
            threadPoolExecutor.execute(()-> {
                System.out.println(Thread.currentThread().getName() + "辦理業務");
            });
        }
    }
}
======================
輸出:
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.john.demo01.MyThreadPoolDemo02$$Lambda$1/901506536@7c75222b rejected from java.util.concurrent.ThreadPoolExecutor@4c203ea1[Running, pool size = 3, active threads = 1, queued tasks = 2, completed tasks = 1]
	at java.base/java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2104)
	at java.base/java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:848)
	at java.base/java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1397)
	at com.john.demo01.MyThreadPoolDemo02.main(MyThreadPoolDemo02.java:26)
pool-1-thread-1辦理業務
pool-1-thread-2辦理業務
pool-1-thread-1辦理業務
pool-1-thread-3辦理業務
pool-1-thread-1辦理業務
pool-1-thread-2辦理業務

(2)CallerRunsPolicy:將某些任務退回給呼叫者,從而降低新任務的流量,

public class MyThreadPoolDemo02 {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                ......
                new ThreadPoolExecutor.CallerRunsPolicy()
        );
        ......
}
===============
輸出:
pool-1-thread-1辦理業務
pool-1-thread-1辦理業務
pool-1-thread-1辦理業務
main辦理業務
pool-1-thread-1辦理業務
pool-1-thread-1辦理業務
pool-1-thread-1辦理業務
pool-1-thread-2辦理業務
pool-1-thread-1辦理業務
pool-1-thread-3辦理業務

(3)DiscardOldestPolicy:拋棄佇列中最久的任務,把新任務添加到佇列中

public class MyThreadPoolDemo02 {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                ......
                new ThreadPoolExecutor.DiscardOldestPolicy()
        );
        for (int i = 0; i < 10; i++) {
            //TimeUnit.SECONDS.sleep(1);
            threadPoolExecutor.execute(()-> {
                System.out.println(Thread.currentThread().getName() + "辦理業務");
            });
        }
}
===========
輸出:
pool-1-thread-1辦理業務
pool-1-thread-3辦理業務
pool-1-thread-2辦理業務
pool-1-thread-3辦理業務
pool-1-thread-1辦理業務
pool-1-thread-2辦理業務

(4)DiscardPolicy:丟棄無法處理的任務,不予任何處理,也不拋出例外,如允許任務失敗,這是一種最好的策略,

public class MyThreadPoolDemo02 {
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                ......
                new ThreadPoolExecutor.DiscardPolicy()
        );
        ......
}
===========
輸出:
pool-1-thread-1辦理業務
pool-1-thread-2辦理業務
pool-1-thread-1辦理業務
pool-1-thread-2辦理業務
pool-1-thread-1辦理業務
pool-1-thread-3辦理業務

(Runtime.getRuntime().availableProcessors()獲取CPU的最大核數,CPU密集型最大執行緒數一般最大核數+1)

五、信號量工具類

1、CountDownLatch 減少計數

CountDownLatch主要方法,當一個或多個執行緒呼叫await方法,這些執行緒會阻塞,其他執行緒呼叫countDown方法會將計數器減1,當計數器減為0時,因await方法阻塞的執行緒會被喚醒,繼續執行,
例子:6個同學陸續離開教室后值班同學才可以關門,代碼如下:

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {

        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; 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() + "班長關門走人");
    }
}

2、CycliBarrier回圈柵欄

CycliBarrier要做的事情是讓一組執行緒達到一個屏障時被阻塞,之后最后一個執行緒達到屏障,屏障才會開門,所有執行緒才會繼續干活,執行緒進入屏障通過CycliBarrier的await方法,
例子:集齊7顆龍珠就可以召喚神龍

public class CyclicBarrierDemo {
    public static void main(String[] args)  {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()->{
            System.out.println("召喚神龍");
        });
        for (int i = 1; i <= 7; i++) {
            final int tmp = i;
            new Thread(() -> {
                try {
                    System.out.println("拿到第" + tmp + "顆龍龍珠");
                    cyclicBarrier.await();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

3、Semaphore信號燈

Semaphore一般用于多個共享資源的互斥和并發執行緒數的控制,通過acquire獲取信號量(信號量減1),要么一直等待下去,直到有執行緒release釋放信號量(信號量+1),喚醒等待的執行緒,
例子:搶車位,6輛車,只有3個車位,使用Semaphore進行流量控制的代碼如下:

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; 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 (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            }, String.valueOf(i)).start();
        }
    }
}

六、異步回呼

異步回呼使用的CompletableFuture類,它的用法與JS前端開發的異步呼叫箭頭函式有點相似,其方法引數大多為函式式介面,因此我們需先線了解一下阿函式式介面的使用,Java內置有四大函式式介面:
在這里插入圖片描述
使用CompletableFuture完成異步回呼,代碼如下:

public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //異步回呼
        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("有回傳值,insert mysql ok");
            //int a = 1 / 0;
            return 1024;
        });
        Integer result = completableFuture2.whenComplete((t, u) -> {
            System.out.println("******t  " + t);
            System.out.println("******u  " + u);

        }).exceptionally(f -> {
            System.out.println(f.getMessage());
            return 444;
        }).get();
        System.out.println(result);
    }
}
============
輸出:
有回傳值,insert mysql ok
******t  1024
******u  null
1024

如果在異步回呼程序中產出例外,執行exceptionally中的方法:

public class CompletableFutureDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException 
        //異步回呼
        CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {
            System.out.println("有回傳值,insert mysql ok");
            int a = 1 / 0;  //人為加入例外操作
            return 1024;
        });

        //
        Integer result = completableFuture2.whenComplete((t, u) -> {
            System.out.println("******t  " + t);
            System.out.println("******u  " + u);

        }).exceptionally(f -> {
            System.out.println(f.getMessage());
            return 444;
        }).get();
        System.out.println(result);

    }
}
==========
輸出:
有回傳值,insert mysql ok
******t  null
******u  java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
java.lang.ArithmeticException: / by zero
444

綜上,對于JUC并發編程的總結完畢,學而時習之,不亦說乎?通過這種理論,實踐,小總結的方式加深了對JUC的理解,

參考資料:
(1)尚硅谷周陽老師的JUC課程
(2)阿里巴巴Java開發手冊

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

標籤:其他

上一篇:前端?規模構建演進實踐

下一篇:運維基本功(十六):遠程管理SSH服務

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