java -- 線程

来源:https://www.cnblogs.com/paopaoT/archive/2023/04/13/17316036.html
-Advertisement-
Play Games

文件操作 文件讀寫 語法:open(file, mode, encoding) 參數:file —— 文件所在位置(相對路徑、絕對路徑) mode —— 操作文件的模式 encoding —— 文件的編碼格式 相對路徑:基於目前的路徑獲取 絕對路徑:一個完整的路徑 操作文件的模式:r-讀 w-寫 a ...


線程與進程

進程:是指一個記憶體中運行的應用程式,每個進程都有一個獨立的記憶體空間,一個應用程式可以同時運行多個進程;進程也是程式的一次執行過程,是系統運行程式的基本單位;系統運行一個程式即是一個進程從創建、運行到消亡的過程。
線程:是進程中的一個執行單元,負責當前進程中程式的執行,一個進程中至少有一個線程。一個進程中是可以有多個線程的,這個應用程式也可以稱之為多線程程式。

進程與線程的區別

進程:有獨立的記憶體空間,進程中的數據存放空間(堆空間和棧空間)是獨立的,至少有一個線程。
線程:堆空間是共用的,棧空間是獨立的,線程消耗的資源比進程小的多。

  1. 因為一個進程中的多個線程是併發運行的,那麼從微觀角度看也是有先後順序的,哪個線程執行完全取決於 CPU 的調度,程式員是干涉不了的。而這也就造成的多線程的隨機性。
  2. Java 程式的進程裡面至少包含兩個線程,主進程也就是 main()方法線程,另外一個是垃圾回收機制線程。每當使用 java 命令執行一個類時,實際上都會啟動一個 JVM,每一個 JVM 實際上就是在操作系統中啟動了一個線程,java 本身具備了垃圾的收集機制,所以在 Java 運行時至少會啟動兩個線程。
  3. 由於創建一個線程的開銷比創建一個進程的開銷小的多,那麼我們在開發多任務運行的時候,通常考慮創建多線程,而不是創建多進程。

線程的創建

繼承Thread類

Java使用java.lang.Thread類代表線程,所有的線程對象都必須是Thread類或其子類的實例。
Java中通過繼承Thread類來創建啟動多線程的步驟如下:

  1. 定義一個類繼承Thread類
  2. 重寫run 方法(線程任務)
  3. 開啟線程
    創建子類對象
    調用thread類的start方法
class MyThread extends Thread {
	@Override
	public void run() {
		for (int i = 0; i < 50; i++) {
			System.out.println("sub... "+i);
		}
	}
}
public static void main(String[] args) {
    // 創建自定義的線程對象
    MyThread mt = new MyThread();
    // 開啟線程
    mt.start();
	// 一個線程僅可以被啟動一次
	// mt.start()
    // 主線程執行
    for (int i = 0; i < 50; i++) {
    	System.out.println("main... "+i);
    }
}

線程名字的設置和獲取

  • Thread類的方法String getName()可以獲取到線程的名字。

  • Thread類的方法setName(String name)設置線程的名字。

  • 通過Thread類的構造方法Thread(String name)也可以設置線程的名字。

public class Demo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        //設置線程名字
        mt.setName("旺財");

        mt.start();
    }
}
class MyThread  extends Thread{
    public void run(){
        System.out.println("線程名字:"+super.getName());
    }
}

線程是有預設名字的,如果不設置,JVM會賦予線程預設名字Thread-0,Thread-1。

獲取運行main方法線程的名字

Demo類不是Thread的子類,因此不能使用getName()方法獲取。
Thread類定義了靜態方法static Thread currentThread()獲取到當前正在執行的線程對象。

public static void main(String[] args){
	Thread t = Thread.currentThread();
	System.out.println(t.getName());
}

實現Runnable介面

java.lang.Runnable介面類
步驟如下:

  1. 定義Runnable介面的實現類,並重寫該介面的run()方法,該run()方法的方法體同樣是該線程的線程執行體。
  2. 創建Runnable實現類的實例,並以此實例作為Thread的target來創建Thread對象,該Thread對象才是真正的線程對象。
  3. 調用線程對象的start()方法來啟動線程。
public class Demo {
    public static void main(String[] args) {
        // 創建自定義類對象  線程任務對象
        MyRunnable mr = new MyRunnable();
        // 創建線程對象
        Thread t = new Thread(mr);
        t.start();
        for (int i = 0; i < 20; i++) {
            System.out.println("main " + i);
        }
    }
}
public class MyRunnable implements Runnable{
    public void run() {
        for (int i = 0; i < 20; i++) {
        System.out.println(Thread.currentThread().getName()+" "+i);
        }
    }
}

通過實現Runnable介面,使得該類有了多線程類的特征。run()方法是多線程程式的一個執行目標。所有的多線程代碼都在run方法裡面。Thread類實際上也是實現了Runnable介面的類。

在啟動的多線程的時候,需要先通過Thread類的構造方法Thread(Runnable target) 構造出對象,然後調用Thread對象的start()方法來運行多線程代碼。

實際上所有的多線程代碼都是通過運行Thread的start()方法來運行的。

匿名內部類方式創建線程

使用線程的內匿名內部類方式,可以方便的實現每個線程執行不同的線程任務操作。

public static void main(String[] args) {
        // 第一種方式 實際上需要的就是Thread類的子類對象
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("方式一 賦值!");
            }
        };
        t.start();

        new Thread() {
            @Override
            public void run() {
                System.out.println("方式一 直接調用!");
            }
        }.start();

        // 第二種方式 實際上需要的就是Runnable介面的實現類對象
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("方式二 賦值!");
            }
        };
        new Thread(r).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("方式二 直接調用!");
            }
        }).start();
    }

Thread和Runnable的區別

如果一個類繼承Thread,則不適合資源共用。但是如果實現了Runable介面的話,則很容易的實現資源共用。
總結:

實現Runnable介面比繼承Thread類所具有的優勢:

  1. 適合多個相同的程式代碼的線程去共用同一個資源。
  2. 可以避免java中的單繼承的局限性。
  3. 增加程式的健壯性,實現解耦操作,代碼可以被多個線程共用,代碼和線程獨立。

Thread類API

睡眠sleep

public static void sleep(long time) 讓當前線程進入到睡眠狀態,到毫秒後自動醒來繼續執行

public class Test{
  public static void main(String[] args){
    for(int i = 1;i<=5;i++){
      	Thread.sleep(1000);
        System.out.println(i)
    }
  }
}

設置線程優先順序

線程的切換是由線程調度控制的,我們無法通過代碼來干涉,但是我們通過提高線程的優先順序最大程度的改善線程獲取時間片的幾率

線程的優先順序被劃分為10級,值分別為1-10,其中1最低,10最高.Thread提供了3個常量來表示:

public static final int MIN_PRIORITY   //1 最低優先順序
public static final int NORM_PRIORITY  //5 預設優先順序
public static final int MAX_PRIORITY   //10 最大優先順序
// 更改線程的優先順序
public final void setPriority(int newPriority)
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println("t1: " + i);
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println("t2: " + i);
                }
            }
        });

        // 設置優先順序
        t1.setPriority(Thread.MAX_PRIORITY);
        t2.setPriority(Thread.MIN_PRIORITY);

        t1.start();
        t2.start();
    }

join

讓當前線程等待,調用方法的線程進行插隊先執行,執行完畢後,在讓當前線程執行.對其他線程沒有任何影響.

註意 此處的當前線程不是調用方法的線程 而是Thread.currentThread().

public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println("t1: " + i);
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println("t2: " + i);
                }
            }
        });

        t1.start();
        // t1.join();
        /*  t1在此處插隊時
            此時 Thread.currentThread() 為 main 線程, 所以 main 線程等待, t1 線程插隊
            由於main 線程等待 t2線程還未開啟, 因此t1執行完畢, main和t2搶
         */

        t2.start();

        t1.join();
        /*  t1在此處插隊時
            此時 Thread.currentThread() 依然為 main 線程, 所以 main 線程等待, t1 線程插隊
            而在這之前 t2 線程已經開啟, 因此會出現兩種情況:
                1. t2 先執行完 t1 執行完之後 主線程執行
                2. t1 先執行完 t2 和 主線程搶
         */

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }

    }
面試題

三個線程同時開啟 保證線程一之後執行線程二 再之後執行線程三

class JoinMethodTest {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    System.out.println("t1: " + i);
                }
            }
        });

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 50; i++) {
                    System.out.println("t2: " + i);
                }
            }
        });

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                for (int i = 0; i < 50; i++) {
                    System.out.println("t3: " + i);
                }
            }
        });

        t1.start();
        t2.start();
        t3.start();

    }
}

線程停止

public final void stop()  直接中斷線程 此方法已過時 不安全
public boolean isInterrupted() 獲取線程是否中斷的狀態 如果中斷返回true 沒中斷返回false
public void interrupt()  中斷線程 此時調用interrupted方法 會返回true

代碼演示:

public class Test02 {
    public static void main(String[] args)  {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {

                for (int i = 0; i <1000000 ; i++) {
                    System.out.println(i);
                    //是否有人要中斷線程 如果有返回true 如果沒有返回false
                    //讓線程中斷更為平滑 可以使用代碼來控制中斷
                    boolean b = Thread.currentThread().isInterrupted();
                    if(b){
                        break;
                    }
                }
            }
        });

        t1.start();

        try {
            Thread.sleep(2000);
//            t1.stop(); //方法已經過時 不安全
            t1.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

用戶線程與守護線程

java分為兩種線程:用戶線程和守護線程

/*
    用戶線程
        我們正常寫的代碼都是用戶線程
    守護線程
        用戶線程存在 守護線程可以執行 用戶線程執行完成 守護線程即使沒有執行完 jvm也會退出

    守護線程和用戶線程沒有本質的區別,唯一不同之處就在於虛擬機的退出:
    如果用戶線程已經全部退出運行了,只剩下守護線程存在,虛擬機會直接退出.但是只要有用戶線程運行,虛擬機就不會退出.

    設置守護線程:
        public final void setDaemon(boolean on) on的值為true將線程設置為守護線程,需要在開啟線程之前設置
*/
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    try {
                        Thread.sleep(200);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("t1: " + i);
                }
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 50; i++) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("t2: " + i);
                }
            }
        });

        /*
            由於t2每次休眠50ms, 而t1休眠200ms, 所以t2先執行完
            而t1並不是守護線程, 所以t2執行完後, t1繼續執行
         */
        // t1.start();
        // t2.start();

        //將t1設置為守護線程, t2執行完後, t1將不再執行, jvm直接退出
        t1.setDaemon(true);
        t1.start();
        t2.start();
    }

線程安全

多條線程操作同一資源時 可能出現數據錯誤 此時線程是不安全的

public class Demo {
    public static void main(String[] args) {
        SellTickets st = new SellTickets();
        Thread t1 = new Thread(st);
        Thread t2 = new Thread(st);
        Thread t3 = new Thread(st);

        t1.start();
        t2.start();
        t3.start();
    }
}
class SellTickets implements Runnable {
    private int i = 20;
    public void run() {
        while (true) {
            if (i > 0){
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 賣票 " + i--);
            }
        }
    }
}
/*
Thread-2 賣票 19
Thread-0 賣票 20
Thread-1 賣票 18
Thread-1 賣票 17
Thread-2 賣票 15
Thread-0 賣票 16
Thread-2 賣票 14
Thread-0 賣票 13
Thread-1 賣票 12
Thread-1 賣票 10
Thread-0 賣票 9
Thread-2 賣票 11
Thread-2 賣票 8
Thread-0 賣票 8
Thread-1 賣票 6
Thread-0 賣票 5
Thread-2 賣票 4
Thread-1 賣票 3
Thread-2 賣票 2
Thread-1 賣票 1
Thread-0 賣票 0
Thread-2 賣票 -1
*/

發現程式出現了兩個問題:

  1. 相同的票數
  2. 不存在的票,比如0票與-1票,是不存在的。

線程同步

當我們使用多個線程訪問同一資源的時候,且多個線程中對資源有寫的操作,就容易出現線程安全問題。

要解決上述多線程併發訪問一個資源的安全性問題:也就是解決重覆票與不存在票問題,Java中提供了同步機制(synchronized)來解決。

根據案例簡述:

視窗1線程進入操作的時候,視窗2和視窗3線程只能在外等著,視窗1操作結束,視窗1和視窗2和視窗3才有機會進入代碼去執行。
也就是說在某個線程修改共用資源的時候,其他線程不能修改該資源,等待修改完畢同步之後,才能去搶奪CPU資源,完成對應的操作,保證了數據的同步性,解決了線程不安全的現象。

為了保證每個線程都能正常執行原子操作,Java引入了線程同步機制。

同步代碼塊

同步代碼塊:線程操作的共用數據進行同步。synchronized關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。

// 格式
synchronized(同步鎖){
     需要同步操作的代碼
}

同步鎖:

同步鎖又稱為對象監視器。同步鎖只是一個概念,可以想象為在對象上標記了一個鎖.

  1. 鎖對象 可以是任意類型。
  2. 多個線程對象 要使用同一把鎖。

註意:在任何時候,最多允許一個線程擁有同步鎖,誰拿到鎖就進入代碼塊,其他的線程只能在外等著(BLOCKED)。

使用同步代碼塊解決代碼:

public class Ticket implements Runnable{
	private int ticket = 100;
	private Object lock = new Object();
	/*
	 * 執行賣票操作
	 */
	@Override
	public void run() {
		//每個視窗賣票的操作 
		//視窗 永遠開啟 
		while(true){
			synchronized (lock) {
				if(ticket>0){//有票 可以賣
					//出票操作
					//使用sleep模擬一下出票時間 
					try {
						Thread.sleep(50);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					//獲取當前線程對象的名字 
					String name = Thread.currentThread().getName();
					System.out.println(name+"正在賣:"+ticket--);
				}
			}
		}
	}
}

註意:線程運行至同步代碼塊的時候,需要判斷鎖,獲取鎖,出去同步代碼塊後要釋放鎖,增加了很多操作,因此線程安全,程式的運行速度慢!

同步方法

同步方法:當一個方法中的所有代碼,全部是線程操作的共用數據的時候,可以將整個方法進行同步。使用synchronized修飾的方法,就叫做同步方法,保證A線程執行該方法的時候,其他線程只能在方法外等著。

// 格式
public synchronized void method(){
   // 可能會產生線程安全問題的代碼
}

使用同步方法代碼如下:

public class Ticket implements Runnable{
	private int ticket = 100;
	/*
	 * 執行賣票操作
	 */
	@Override
	public void run() {
		//每個視窗賣票的操作 
		//視窗 永遠開啟 
		while(true){
			sellTicket();
		}
	}
	
	/*
	 * 鎖對象 是 誰調用這個方法 就是誰 
	 *   隱含 鎖對象 就是  this
	 *    
	 */
	public synchronized void sellTicket(){
        if(ticket>0){//有票 可以賣	
            //出票操作
            //使用sleep模擬一下出票時間 
            try {
              	Thread.sleep(100);
            } catch (InterruptedException e) {
              	e.printStackTrace();
            }
            //獲取當前線程對象的名字 
            String name = Thread.currentThread().getName();
            System.out.println(name+"正在賣:"+ticket--);
        }
	}
}

同步鎖是誰?

  • 對於非static方法,同步鎖就是this。

  • 對於static方法,我們使用當前方法所在類的位元組碼對象(類名.class)。

Lock鎖

java.util.concurrent.locks.Lock機制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作,同步代碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現面向對象。

Lock鎖也稱同步鎖,加鎖與釋放鎖方法化了,如下:

  • public void lock() :加鎖。
  • public void unlock():釋放鎖。

使用如下:

public class Ticket implements Runnable{
	private int ticket = 100;
	
	Lock lock = new ReentrantLock();
	/*
	 * 執行賣票操作
	 */
	@Override
	public void run() {
		//每個視窗賣票的操作 
		//視窗 永遠開啟 
		while(true){
			lock.lock();
			if(ticket>0){//有票 可以賣
				//出票操作 
				//使用sleep模擬一下出票時間 
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				//獲取當前線程對象的名字 
				String name = Thread.currentThread().getName();
				System.out.println(name+"正在賣:"+ticket--);
			}
			lock.unlock();
		}
	}
}

線程狀態

這裡先列出各個線程狀態發生的條件,下麵將會對每種狀態進行詳細解析。

線程狀態 導致狀態發生條件
NEW(新建) 線程剛被創建,但是並未啟動。還沒調用start方法。
Runnable(可運行) 線程可以在java虛擬機中運行的狀態,可能正在運行自己代碼,也可能沒有,這取決於操作系統處理器。
Blocked(鎖阻塞) 當一個線程試圖獲取一個對象鎖,而該對象鎖被其他的線程持有,則該線程進入Blocked狀態;當該線程持有鎖時,該線程將變成Runnable狀態。
Waiting(無限等待) 一個線程在等待另一個線程執行一個(喚醒)動作時,該線程進入Waiting狀態。進入這個狀態後是不能自動喚醒的,必須等待另一個線程調用notify或者notifyAll方法才能夠喚醒。
Timed Waiting(計時等待) 同waiting狀態,有幾個方法有超時參數,調用他們將進入Timed Waiting狀態。這一狀態將一直保持到超時期滿或者接收到喚醒通知。帶有超時參數的常用方法有Thread.sleep 、Object.wait。
Teminated(被終止) 因為run方法正常退出而死亡,或者因為沒有捕獲的異常終止了run方法而死亡。

等待和喚醒

Object類的方法

void wait()
// 在其他線程對用此對象的notify()方法或notifyAll方法前, 導致當前線程等待
void wait(long time)
// 當前線程等待 time 毫秒
void notify()
// 喚醒在此對象監視器上等待的單個線程
void notifyAll()
// 喚醒在此對象監視器上等待的所有線程
public class RelatedInObject {
    public static void main(String[] args) throws InterruptedException {
        Object o = new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (o) {
                    System.out.println("開始等待");
                    try {
                        o.wait();
                         // 等待3000毫秒後
                         // o.wait(3000);
                        System.out.println("已經被喚醒");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("end");
            }
        }).start();

        Thread.sleep(5000);

        synchronized (o) {
            System.out.println("開始喚醒");
            o.notify();
        }
    }
}

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

-Advertisement-
Play Games
更多相關文章
  • Web 前端自動化測試是一種可以提高測試效率、減少測試成本和提高測試質量的方法,適用於各種類型的 Web 應用程式。本文談談前端自動化測試從入門到精通再到專家級的方案與思維! ...
  • 配載代表著某條線路是否具有發往某個方向(區域、省市縣、分揀等)的能力,也可以說是網點(分揀中心)是否具有承載配載所指方向貨物的能力。一般網路規劃者,在均衡線路間貨量時,會通過調整配載來完成。線路上可允許配載貨物的“產品類型、最終妥投目的地”,通過線路的配載,計算 當前網點 到 目的網點 的 下一個網... ...
  • Python使用基本規 (一)、關於註釋 註釋是編程語言必備的,以下是幾種常用的註釋方式。 1 # 這是單行註釋 2 3 4 ''' 這是多行註釋1 ''' 5 6 7 """ 這是多行註釋2 """ (二)、關於列印 與C#中的WriteLine相似,Python中Print 的輸出是預設換行的, ...
  • 前言 在上一篇文章中,我們介紹了^運算符的高級用法,本篇文章,我們將介紹~ 運算符的一些高級用法。 一、人物簡介 第一位閃亮登場,有請今後會一直教我們C語言的老師 —— 自在。 第二位上場的是和我們一起學習的小白程式猿 —— 逍遙。 二、相反數 我們可以利用負數的補碼性質,來獲得一個正數的相反數 # ...
  • 經過了幾天的奮戰,終於把微信服務號的模板消息給寫完了。後端其實沒花多少時間,因為之前已經有同學提過pull request了,我在這基礎之上簡單優化下就完事了,主要的時間都是花在前端上,對前端頁面和參數的適配比較麻煩。 消息推送平臺🔥推送下發【郵件】【簡訊】【微信服務號】【微信小程式】【企業微信】 ...
  • 1、概念:docker是一個開源的應用容器引擎,docker可以讓開發者打包他們的應用以及依賴環境包到一個輕量級、可移值的容器中。然後發佈到任何流行的linux機器上。 安裝過程: 1、yum包更新到最新 yum update 2、安裝需要的軟體包 yum install -y yum-utils ...
  • 長字元串起因 項目裡面有一長串的加密字元串(最長的萬多個字元),需要拼接作為參數發送給第三方。 如果我們使用 枚舉 定義的話,idea 編譯的時候就會出現編譯報錯 Error: java:常量字元串過長 解決想法 網上還有一個說法,說是編譯器問題,修改 idea 工具的編譯為 eclipse 即可。 ...
  • 說下場景,我的程式在多線程場景下一個迴圈體中處理業務數據,其中需要調用一個外部http介面去獲取一些數據,程式總會在在本地執行一段時間後會拋出Address already in use: no further information錯誤。 這是大量併發場景下出現的問題,經過查閱原因是OkHttp的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...