Java多線程基礎入門(已完結)

来源:https://www.cnblogs.com/yuhaozhe/archive/2022/08/09/16570676.html
-Advertisement-
Play Games

Java多線程基礎入門 參考:b站-狂神-多線程詳解 練習與演示代碼見gitee:https://gitee.com/yuhaozhee/java-learning-record ...


線程簡介

任務

程式

進程 Process?執行程式的一次執行過程,是一個動態的概念。是系統資源分配的單位

線程 Thread?一個進程中可以包含若幹個線程,進程中至少有一個線程。線程是CPU調度和執行的單位

模擬多線程

線程實現(重點)

線程的創建

一、Thread類

1、使用方法

1.自定義線程類繼承Thread類

2.重寫run()方法,編寫線程執行體

3.創建線程對象,調用start()方法啟動線程

2、代碼示例

//創建線程方式一:繼承Thread類,重寫run()方法,調用start()開啟線程
public class TestThread1 extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("Thread----:"+i);
        }
    }

    public static void main(String[] args) {
        //main線程,主線程
        //創建一個線程對象
        TestThread1 testThread1=new TestThread1();
        //調用方法開啟線程
        testThread1.start();
        for (int i = 0; i < 10; i++) {
            System.out.println("Main--------:"+i);
        }
    }
}

3、運行結果

Main--------:0
Main--------:1
Thread----:0
Thread----:1
Main--------:2
Thread----:2
Main--------:3
Main--------:4
Main--------:5
Main--------:6
Thread----:3
Main--------:7
Main--------:8
Main--------:9
Thread----:4
Thread----:5
Thread----:6
Thread----:7
Thread----:8
Thread----:9

4、小結

線程開啟不一定立即執行,而是由CPU選擇調度執行

5、案例

下載圖片

//練習Thread,實現多線程同步下載圖片
public class TestThread2 extends Thread{
    private String url;
    private String name;
    public TestThread2(String url, String name) {
        this.url = url;
        this.name = name;
    }
    @Override
    public void run() {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下載了文件名為:"+name);
    }
    public static void main(String[] args) {
        TestThread2 testThread2_1 = new TestThread2("https://img2.baidu.com/it/u=287632534,2172937882&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=309","CSGO1.jpg");
        TestThread2 testThread2_2 = new TestThread2("https://img0.baidu.com/it/u=3909898413,2777003333&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","CSGO2.jpg");
        TestThread2 testThread2_3 = new TestThread2("https://img0.baidu.com/it/u=1130153521,1400899467&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","CSGO3.jpg");
        testThread2_1.start();
        testThread2_2.start();
        testThread2_3.start();
    }
}
class WebDownloader{
    public void downloader(String url,String name) {
        try {
            FileUtils.copyURLToFile(new URL(url),new File(name));
        } catch (IOException e) {
            e.printStackTrace();
            System.out.println("IO異常");
        }
    }
}

二、Runnable介面

1、使用方法

1.定義TestThread3類實現Runnable介面

2.實現run()方法,編寫線程執行體

3.創建線程對象,傳入testThread3對象,調用start()方法啟動線程

代理方法

2、代碼示例

public class TestThread3 implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("run:"+i);
        }
    }
    public static void main(String[] args) {
        TestThread3 testThread3=new TestThread3();
        new Thread(testThread3).start();
        for (int i = 0; i < 10; i++) {
            System.out.println("main:"+i);
        }
    }
}

3、運行結果

main:0
run:0
main:1
run:1
main:2
run:2
main:3
run:3
main:4
run:4
main:5
run:5
main:6
run:6
main:7
run:7
main:8
run:8
main:9
run:9

4、對比

繼承Thread類

子類繼承Thread類具備多線程能力

通過子類對象.start() 啟動

不建議使用:避免OOP單繼承局限性

實現Runnable介面

實現介面Runnable具有多線程能力

傳入目標對象+Thread對象.start() 啟動 代理模式 一份資源多個代理

StartThread station=new StartThread();
new Thread(station,"小明").start();
new Thread(station,"小紅").start();
new Thread(station,"小剛").start();

建議使用:避免單繼承局限性,靈活方便,方便同一個對象被多個線程使用

5、初識併發問題

購買車票場景問題

代碼

//多個線程同時操作同一個對象
//買火車票的例子
public class TestThread4 implements Runnable{
    private int ticketNums=10;
    @Override
    public void run() {
        while (true){
            if (ticketNums<=0){
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+":拿到了第"+ticketNums--+"張票");
        }
    }
    public static void main(String[] args) {
        TestThread4 testThread4=new TestThread4();
        new Thread(testThread4,"1號線程").start();
        new Thread(testThread4,"2號線程").start();
        new Thread(testThread4,"3號線程").start();
    }
}

結果

1號線程:拿到了第10張票
3號線程:拿到了第9張票
2號線程:拿到了第8張票
2號線程:拿到了第6張票
3號線程:拿到了第7張票
1號線程:拿到了第6張票
1號線程:拿到了第5張票
2號線程:拿到了第5張票
3號線程:拿到了第4張票
3號線程:拿到了第3張票
2號線程:拿到了第1張票
1號線程:拿到了第2張票

車票重覆購買:多個線程同時操作同一個資源的情況下,線程不安全,數據紊亂

龜兔賽跑問題
//模擬龜兔賽跑
public class Race implements Runnable{
    private static String winner;
    @Override
    public void run() {
        for (int i = 0; i <=100; i++) {
            if (Thread.currentThread().getName()=="兔子"&&i%10==0){
                try {
                    Thread.sleep(2 );
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            boolean flag=gameOver(i);
            if(flag) break;
            System.out.println(Thread.currentThread().getName()+"跑了:"+i+"步");
        }
    }
    private boolean gameOver(int steps){
        if (winner!=null){
            return true;
        }else {
            if (steps==100){
                winner=Thread.currentThread().getName();
                System.out.println("winner is"+winner);
                return true;
            }
        }
        return false;
    }
    public static void main(String[] args) {
        Race race=new Race();
        new Thread(race,"兔子").start();
        new Thread(race,"烏龜").start();
    }
}

三、Callable介面(瞭解即可)

1、使用方法

1.實現Callable介面,需要返回值類型

2.實現call方法,需要拋出異常

3.創建目標對象

4.創建執行服務:ExecutorService ser=Executors.newFixedThreadPool(1);

5.提交執行:Future result1=ser.submit(t1);

6.獲取結果:boolean r1=result1.get();

7.關閉服務:ser.shutdownNow();

2、代碼示例

public class TestCallable implements Callable<Boolean> {
    //實現call方法,需要拋出異常
    @Override
    public Boolean call() throws Exception {
        WebDownloader webDownloader = new WebDownloader();
        webDownloader.downloader(url,name);
        System.out.println("下載了文件名為:"+name);
        return true;
    }

    private String url;
    private String name;

    public TestCallable(String url, String name) {
        this.url = url;
        this.name = name;
    }
    
    public static void main(String[] args) {
        //創建目標對象
        TestCallable testThread2_1 = new TestCallable("https://img2.baidu.com/it/u=287632534,2172937882&fm=253&fmt=auto&app=138&f=JPEG?w=504&h=309","CSGO11.jpg");
        TestCallable testThread2_2 = new TestCallable("https://img0.baidu.com/it/u=3909898413,2777003333&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","CSGO21.jpg");
        TestCallable testThread2_3 = new TestCallable("https://img0.baidu.com/it/u=1130153521,1400899467&fm=253&fmt=auto&app=120&f=JPEG?w=1422&h=800","CSGO31.jpg");
        //創建執行服務
        ExecutorService ser= Executors.newFixedThreadPool(3);
        //提交執行
        Future<Boolean> submit1 = ser.submit(testThread2_1);
        Future<Boolean> submit2 = ser.submit(testThread2_2);
        Future<Boolean> submit3 = ser.submit(testThread2_3);
        //獲取結果
        try {
            boolean rs1 = submit1.get();
            boolean rs2 = submit2.get();
            boolean rs3 = submit3.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        //關閉服務
        ser.shutdownNow();
    }
}
靜態代理模式
public class StacticProxy {
    public static void main(String[] args) {
        WeddingCompany weddingCompany=new WeddingCompany(new You());
        weddingCompany.HappyMarry();
    }


}
interface Marry{
    void HappyMarry();
}
//真實角色
class You implements Marry{
    @Override
    public void HappyMarry() {
        System.out.println("marry");
    }
}
//代理角色
class WeddingCompany implements Marry{
    private Marry target;

    public WeddingCompany(Marry target) {
        this.target = target;
    }

    @Override
    public void HappyMarry() {
        before();
        this.target.HappyMarry();
        after();
    }

    private void after() {
        System.out.println("收尾款");
    }

    private void before() {
        System.out.println("佈置現場");
    }
}

代理對象可以做很多真實對象做不了的事情,真實對象專註做自己的事情

Lambda表達式

函數式編程

為什麼要使用Lambda表達式?避免匿名內部類過多,讓代碼看起來簡潔,去掉無意義的代碼只留下核心的邏輯

什麼是函數式介面?任何介面只包含唯一一個抽象方法,則它就是一個函數式介面

public interface Runnable{
	public abstract void run();
}

對於函數式介面,我們可以通過lambda表達式來創建該介面的對象

推導過程

//推導Lambda表達式
public class TestLambda1 {
    //3.靜態內部類
    static  class Like2 implements ILike{
        @Override
        public void lambda() {
            System.out.println("I Like Lambda2");
        }
    }

    public static void main(String[] args) {
        ILike like=new Like();
        like.lambda();
        like=new Like2();
        like.lambda();

        //4.局部內部類
        class Like3 implements ILike{
            @Override
            public void lambda() {
                System.out.println("I Like Lambda3");
            }
        }
        like=new Like3();
        like.lambda();

        //5.匿名內部類
        like=new ILike() {
            @Override
            public void lambda() {
                System.out.println("I Like Lambda,匿名內部類");
            }
        };
        like.lambda();

        //6.用Lambda簡化
        like=()->{
            System.out.println("Lambda!!!");
        };
        like.lambda();
    }
}
//1.定義一個介面
interface ILike{
    void lambda();
}
//2.實現類
class Like implements ILike{
    @Override
    public void lambda() {
        System.out.println("I Like Lambda");
    }
}

線程狀態

一、五大狀態

線程有五大狀態:創建(new)、就緒(start)、阻塞(sleep、wait)、運行(執行)、死亡

創建:Thread t=new Thread(),線程對象一旦創建就進入到了新生狀態

就緒:創建後,調用start()方法,線程立即進入就緒狀態,但不意味著立即調度執行

阻塞:當調用sleep()、wait()或同步鎖定時,線程進入阻塞狀態,就是代碼不往下執行,阻塞事件解除後,重新進入就緒狀態,等待CPU調度執行

運行:進入運行狀態,線程才真正執行線程體的代碼塊

死亡:線程中斷或者結束,一旦進入死亡狀態,就不能再次啟動

  • Thread.State 線程狀態

NEW: 尚未啟動的線程處於此狀態

RUNNABLE:在Java虛擬機中執行的線程處於此狀態

BLOCKED:被阻塞等待監視器鎖定的線程處於此狀態

WAITING:正在等待另一個線程執行特定動作的線程處於此狀態

TIMED_WAITING:正在等待另一個線程執行動作達到指定等待時間的線程處於此狀態

TERMINATED:已退出的線程處於此狀態

二、線程方法

//更改線程的優先順序
setPriority(int newPriority)
//指定毫秒後讓線程休眠
static void sleep(long millis)
//等待該線程終止
void join()
//暫停正在執行的線程,運行其他的
static void yield()
//中斷線程(不要用這個方式)
void interrupt()
//測試線程是否處於活動狀態
boolean isAlive()

1、線程停止

不推薦JDK提供的stop()、destroy()方法(已廢棄)

推薦讓線程自己停下來,建議使用一個標誌位來終止線程

//測試標誌位flag
public class TestStop implements Runnable {
    private boolean flag = true;
    @Override
    public void run() {
        int i = 0;
        while (flag==true) {
            System.out.println("Thread is running..." + i);
            i++;
        }
    }

    public void stop() {
        this.flag = false;
    }

    public static void main(String[] args) {
        TestStop testStop = new TestStop();
        new Thread(testStop).start();
        System.out.println("Start");
        for (int i = 0; i < 100000; i++) {
            System.out.println("main is running..." + i);
            if (i == 90000) {
                testStop.stop();
                break;
            }
        }
    }
}

2、線程禮讓

作用是讓當前運行的線程暫停,但不阻塞,只是進入就緒狀態

讓CPU重新調度,有可能原本的進程再次運行進去,禮讓不一定成功

public class TestYield{
    public static void main(String[] args) {
        MyYield myYield=new MyYield();
        new Thread(myYield,"A").start();
        new Thread(myYield,"B").start();
    }
}

class MyYield implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" is running...");
        Thread.yield();
        System.out.println(Thread.currentThread().getName()+" is stop...");
    }
}

案例有問題,不一定正確,實際可能與調度演算法有關

3、Join

Join合併線程,待此線程完成後,再執行其他線程,其他線程阻塞,類似”插隊“

public class TestJoin implements Runnable{
    public static void main(String[] args) throws InterruptedException {
        TestJoin testJoin=new TestJoin();
        Thread thread = new Thread(testJoin);
        thread.start();
        for (int i = 0; i < 300; i++) {
            if (i==200){
                thread.join();
            }
            System.out.println("main..."+i);
        }
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("vip..."+i);
        }
    }
}

4、線程狀態

  • Thread.State 線程狀態

NEW: 尚未啟動的線程處於此狀態

RUNNABLE:在Java虛擬機中執行的線程處於此狀態

BLOCKED:被阻塞等待監視器鎖定的線程處於此狀態

WAITING:正在等待另一個線程執行特定動作的線程處於此狀態

TIMED_WAITING:正在等待另一個線程執行動作達到指定等待時間的線程處於此狀態

TERMINATED:已退出的線程處於此狀態

public class TestState {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(".......");
        });
        //觀察狀態
        Thread.State state = thread.getState();
        System.out.println(state);//new
        
        thread.start();
        state = thread.getState();
        System.out.println(state);//run

        while (state!=Thread.State.TERMINATED){
            Thread.sleep(100);
            state = thread.getState();//更新線程狀態
            System.out.println(state);
        }
    }
}

5、線程優先順序

Java提供一個線程調度器來監控監視程式中啟動後進入到就緒狀態的所有線程,線程調度器按照優先順序決定應該調度哪個線程來執行

  • 線程的優先順序用數字表示,範圍從1~10

  • 通過getPriority().setPriority(int xxx)

public class TestPriority {
    public static void main(String[] args) {
        MyPriority myPriority = new MyPriority();
        Thread thread1 = new Thread(myPriority,"thread1");
        Thread thread2 = new Thread(myPriority,"thread2");
        Thread thread3 = new Thread(myPriority,"thread3");
        Thread thread4 = new Thread(myPriority,"thread4");
        Thread thread5 = new Thread(myPriority,"thread5");
        Thread thread6 = new Thread(myPriority,"thread6");

        thread1.start();

        thread2.setPriority(1);
        thread2.start();

        thread3.setPriority(4);
        thread3.start();

        thread4.setPriority(Thread.MAX_PRIORITY);
        thread4.start();

        thread5.setPriority(-1);
        thread5.start();

        thread6.setPriority(11);
        thread6.start();

    }
}
class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
    }
}

優先順序高的不一定先跑

6、守護(daemon)線程

  • 線程分為用戶線程守護線程
  • 虛擬機必須確保用戶線程執行完畢
  • 虛擬機不用等待守護線程執行完畢
  • 例如:後臺記錄操作日誌,監控記憶體,垃圾回收
//測試守護線程
public class TestDaemon {
    public static void main(String[] args) {
        God god=new God();
        You you=new You();
        Thread thread=new Thread(god);
        thread.setDaemon(true);
        thread.start();
        new Thread(you).start();
    }
}
//上帝
class God implements Runnable{
    @Override
    public void run() {
        while(true){
            System.out.println("god bless u");
        }
    }
}
//你
class You implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 365; i++) {
            System.out.println("live...");
        }
        System.out.println("gg...");
    }
}

線程同步(重點)

併發:多個線程操作同一個資源

排隊:多個線程訪問同一個對象,並且某些線程還想修改這個對象,這時我們就需要線程同步,線程同步其實是一種等待機制,多個需要同時訪問此對象的線程進入這個對象的等待池形成隊列,等待前麵線程使用完畢,下一個線程再使用

由於同一個進程的多個線程共用一塊存儲空間,在帶來方便的同時,也帶來了訪問衝突問題,為了保證數據在方法中被訪問時的正確性,在訪問時加入鎖機制 synchronized,當一個線程獲得對象的排他鎖,獨占資源,其他線程必須等待,使用後釋放鎖即可,存在以下問題

  • 一個線程持有鎖會導致其他所有需要此鎖的線程被掛起
  • 在多線程競爭下,加鎖,釋放鎖會導致比較多的上下文切換和調度延時,引起性能問題
  • 如果一個優先順序高的線程等待一個優先順序低的線程釋放鎖,會導致優先順序倒置,引起性能問題

購買車票場景問題2

public class UnsafeBuyTicket {
    public static void main(String[] args) {
        BuyTicket station=new BuyTicket();
        new Thread(station,"小明").start();
        new Thread(station,"小紅").start();
        new Thread(station,"小剛").start();
    }
}
class BuyTicket implements Runnable{
    private int ticketNums=10;
    boolean flag=true;
    @Override
    public void run() {
        //買票
        while (flag){
            try {
                buy();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    public void buy() throws InterruptedException {
        if (ticketNums<=0){
            flag=false;
            return;
        }
        Thread.sleep(100);
        System.out.println(Thread.currentThread().getName()+"拿到了"+ticketNums--);
    }
    public void stop() {
        flag=false;
    }
}

存在重覆購票的情況,線程不安全

此時需要引入線程同步機制

同步方法

由於我們可以通過private關鍵字來保證數據對象只能被方法訪問,所以我們只需要對方法提出一套機制,這套機制就是synchronized關鍵字,它包括兩個用法:synchronized方法和synchronized塊

1.synchronized方法

public synchronized void method(int args){}

synchronized方法控制對“對象”的訪問,每個對象對應一把鎖,每個synchronized方法都必須獲得調用該方法的對象的鎖才能執行,否則線程會阻塞,方法一旦執行,就獨占該鎖,直到方法返回才釋放鎖,後面被阻塞的線程才能獲得這個鎖,繼續執行

缺陷:若將一個大的方法聲明為synchronized將會影響效率

2.synchronized塊

同步塊

synchronized(互斥資源){}

Obj稱之為同步監視器

  • Obj可以是任何對象,但是推薦使用共用資源作為同步監視器
  • 同步方法中無需指定同步監視器,因為同步方法的同步監視器就是this,就是這個對象本身,或者是class

同步監視器的執行過程

1.第一個線程訪問,鎖定同步監視器,執行其中代碼

2.第二個線程訪問,發現同步監視器被鎖定,無法訪問

3.第一個線程訪問完畢,解鎖同步監視器

4.第二個線程訪問,發現同步監視器沒有鎖,然後鎖定並訪問

3.Lock鎖

可重用鎖(ReentrantLock)

public class TestLock {
    public static void main(String[] args) {
        TestLock2 testLock2=new TestLock2();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
        new Thread(testLock2).start();
    }
}
public class TestLock2 implements Runnable{
    int ticketNums=10;
    private final ReentrantLock lock=new ReentrantLock();
    @Override
    public void run() {
        while(true){
            try {
                lock.lock();
                if (ticketNums>=0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticketNums--);
                }else break;
            }finally {
                lock.unlock();
            }
        }
    }
}

synchronized與Lock的對比

  • Lock是顯式鎖(手動開啟和關閉鎖,try開啟,finally關閉),synchronized是隱式鎖,出了作用域自動釋放
  • Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
  • 使用Lock鎖,JVM將使用較少時間來調度線程,性能更好,並且具有更好的擴展性(提供更多子類)
  • 優先使用順序:Lock->同步代碼塊(已經進入了方法體,分配了相應資源)->同步方法(在方法體外)

死鎖

多個線程各自占有一些共用資源,並且互相等待其他線程占用的資源才能運行,而導致兩個或者多個線程都在等待對方釋放資源,都停止執行的情景。某一個同步塊同時擁有“兩個以上對象的鎖”時,就可能發生“死鎖問題”

public class DeadLock {
    public static void main(String[] args) {
        Makeup g1=new Makeup(0,"灰姑娘");
        Makeup g2=new Makeup(1,"白雪公主");
        g1.start();
        g2.start();
    }
}
//口紅
class Lipstick{
}
//鏡子
class Mirror{
}
class Makeup extends Thread{
    static Lipstick lipstick=new Lipstick();
    static Mirror mirror=new Mirror();
    int choice;
    String girlName;

    public Makeup(int choice, String girlName) {
        this.choice = choice;
        this.girlName = girlName;
    }
    @Override
    public void run() {
        try {
            makeup();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    private void makeup() throws InterruptedException {
        if (choice==0){
            synchronized (lipstick){
                System.out.println(this.girlName+"獲得口紅");
                Thread.sleep(500);
                synchronized (mirror){
                    System.out.println(this.girlName+"獲得鏡子");
                    Thread.sleep(500);
                }
            }
        }else {
            synchronized (mirror){
                System.out.println(this.girlName+"獲得鏡子");
                Thread.sleep(500);
                synchronized (lipstick){
                    System.out.println(this.girlName+"獲得口紅");
                    Thread.sleep(500);
                }
            }
        }
    }
}

產生死鎖的四個必要條件

1、互斥條件:一個資源每次只能被一個進程使用。

2、請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放

3、不剝奪條件:進程已獲得的資源,在未使用完之前,不能強行剝奪

4、迴圈等待條件:若幹進程之間形成一種頭尾相接的迴圈等待資源關係

只要破壞其中任意一個或多個就可以避免死鎖發生

線程通信問題

生產者消費者問題

  • 倉庫中只能存放一件物品,生產者將生產出來的產品放入倉庫,消費者將倉庫中產品取走消費
  • 如果倉庫中沒有產品,則生產者將產品放入倉庫,否則停止生產並等待,直到倉庫中的產品被消費者取走為止
  • 如果倉庫中放有產品,則消費者可以將產品取走消費,否則停止消費並等待,直到倉庫再次放入產品為止

這是一個線程同步問題,生產者和消費者共用一個資源,並且生產者和消費者之間互相依賴,互為條件

關鍵方法wait()、notify()

wait:線程釋放鎖等待

notify:喚醒一個等待狀態的線程

管程法

public class TestPC {

    public static void main(String[] args) {
        SynContainer container =new SynContainer();
        new Producer(container).start();
        new Consumer(container).start();
    }
}

class Producer extends Thread{
    SynContainer container;

    public Producer(SynContainer container) {
        this.container = container;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                container.push(new Chicken(i));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("生產了"+i+"只雞");
        }
    }
}
class Consumer extends Thread{
    SynContainer container;

    public Consumer(SynContainer container) {
        this.container = container;
    }
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消費了"+i+"只雞");
            try {
                container.pop();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Chicken{
    int id;

    public Chicken(int id) {
        this.id = id;
    }
}
class SynContainer{
    Chicken[] chickens=new Chicken[10];
    int count=0;
    public synchronized void push(Chicken chicken) throws InterruptedException {
        if (chickens.length==count){
            this.wait();
        }
        chickens[count]=chicken;
        count++;
        this.notifyAll();
    }
    public synchronized Chicken pop() throws InterruptedException {
        if (count==0){
            this.wait();
        }
        count--;
        Chicken chicken = chickens[count];
        this.notifyAll();
        return chicken;
    }
}

信號燈法

線程池

背景:經常創建和銷毀、使用量特別大的資源,比如併發情況下的線程,對性能影響很大

思路:提前創建好多個線程,放入線程池中,使用時直接獲取,使用完放回池中。可以避免頻繁創建銷毀,實現重覆利用。類似生活中的公共交通工具

好處

1.提高響應速度(減少了創建新線程的時間)

2.降低資源消耗(重覆利用線程池中線程,不需要每次都創建)

3.便於線程管理

​ (1)corePoolSize:核心池大小

​ (2)maximumPoolSize:最大線程數

​ (3)keepAliveTime:線程沒有任務時最多保持多長時間後會終止

線程池相關API

ExecutorService和Executors

1.ExecutorService:真正的線程池介面。常見子類ThreadPoolExecutor

​ (1)void execute(Runnable command):執行任務/命令,沒有返回值,一般用來執行Runnable

​ (2)Future submit(Callable task):執行任務,有返回值,一般用來執行Callable

​ (3)void shutdown():關閉線程池

2.Executors:工具類、線程池的工廠類,用於創建並返回不同類型的線程池


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • vivo 互聯網前端團隊-Yang Kun 一、背景 在團隊中,我們因業務發展,需要用到桌面端技術,如離線可用、調用桌面系統能力。什麼是桌面端開發?一句話概括就是:以 Windows 、macOS 和 Linux 為操作系統的軟體開發。對此我們做了詳細的技術調研,桌面端的開發方式主要有 Native ...
  • 蒼穹之邊,浩瀚之摯,眰恦之美; 悟心悟性,善始善終,惟善惟道! —— 朝槿《朝槿兮年說》 寫在開頭 作為一名Java Developer,我們都清楚地知道,主要從搭載Linux系統上的伺服器程式來說,使用Java編寫的是”單進程-多線程"程式,而用C++語言編寫的,可能是“單進程-多線程”程式,“多 ...
  • 技術 Leader 是一個對綜合素質要求非常高的崗位,不僅要有解具體技術問題的架構能力,還要具備團隊管理的能力,更需要引領方向帶領團隊/平臺穿越迷茫進階到下一個境界的能力。所以通常來說技術 Leader 的技能是虛實結合的居多,繁雜的工作偏多。為此我把自己在工作中經常用到的思考技巧也做了一個整理。 ...
  • 社交是一種永恆的需求,既有生存層面的必要,也有情感上的渴求。而隨著互聯網開始統治這個時代,社交被搬到了網上,並且越來越成為主流,社交也在發展成互聯網產品的一個重要賽道。本文將介紹Soul是如何破解Z世代社交密碼的。 文章目錄 01 年輕人的社交密碼 02 為什麼對年輕人來說,Soul是那個對的產品? ...
  • 統一術語(戰略設計) 我們將通過DDD完成業務與技術的完整落地 統一 領域模型術語 DDD模式名稱 技術 技術設計術語 技術術語 技術設計模式 業務 領域模型術語 DDD模式名稱 業務術語 設計無關的業務術語 清晰的事件流 DDD 領域驅動設計是一個有關軟體開發的方法論,它提出基於領域開發的開發模式 ...
  • 3、ElasticSearch搜索結果處理 3.1、排序 Elasticsearch預設是根據相關度算分(_score)來排序,但是也支持自定義方式對搜索結果排序,可以排序的欄位類型有如下幾種 keyword類型 數值類型 地理坐標類型 日期類型 ... 3.1.1、普通欄位排序 keyword、數 ...
  • @Autowired註解是spring用來支持依賴註入的核心利器之一,但是我們或多或少都會遇到required a single bean, but 2 were found(2可能是其他數字)的問題,接下來我們從源碼的角度去看為什麼會出現這個問題,以及這個問題的解法是什麼? 首先我們寫一個demo ...
  • 2、ElasticSearch高級搜索 Elasticsearch提供了基於JSON的DSL(Domain Specific Language)來定義查詢。常見的查詢類型如下所示 ①、查詢所有 查詢出所有數據,一般測試用;例如 match_all 如下圖所示 ②、全文檢索(full text)查詢 ...
一周排行
    -Advertisement-
    Play Games
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...