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
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...