java的concurrent用法詳解

来源:http://www.cnblogs.com/brightestSea/archive/2016/03/14/5274671.html
-Advertisement-
Play Games

Maven引入    我們都知道,在JDK1.5之前,Java中要進行業務併發時,通常需要有程式員獨立完成代碼實現,當然也有一些開源的框架提供了這些功能,但是這些依然沒有JDK自帶的功能使用起來方便。而當針對高質量Java多線程併發程式設計時,為防止死蹦等現象的出現,比如使用java之前的wait(


Maven引入

<dependency>
    <groupId>org.dspace.xmlui.concurrent</groupId>
    <artifactId>concurrent</artifactId>
    <version>1.3.4</version>
</dependency>

  

我們都知道,在JDK1.5之前,Java中要進行業務併發時,通常需要有程式員獨立完成代碼實現,當然也有一些開源的框架提供了這些功能,但是這些依然沒有JDK自帶的功能使用起來方便。而當針對高質量Java多線程併發程式設計時,為防止死蹦等現象的出現,比如使用java之前的wait()、notify()和synchronized等,每每需要考慮性能、死鎖、公平性、資源管理以及如何避免線程安全性方面帶來的危害等諸多因素,往往會採用一些較為複雜的安全策略,加重了程式員的開發負擔.萬幸的是,在JDK1.5出現之後,Sun大神(Doug Lea)終於為我們這些可憐的小程式員推出了java.util.concurrent工具包以簡化併發完成。開發者們藉助於此,將有效的減少競爭條件(race conditions)和死鎖線程。concurrent包很好的解決了這些問題,為我們提供了更實用的併發程式模型。

Executor                  :具體Runnable任務的執行者。
ExecutorService           :一個線程池管理者,其實現類有多種,我會介紹一部分。我們能把Runnable,Callable提交到池中讓其調度。
Semaphore                 :一個計數信號量
ReentrantLock             :一個可重入的互斥鎖定 Lock,功能類似synchronized,但要強大的多。
Future                    :是與Runnable,Callable進行交互的介面,比如一個線程執行結束後取返回的結果等等,還提供了cancel終止線程。
BlockingQueue             :阻塞隊列。
CompletionService         : ExecutorService的擴展,可以獲得線程執行結果的
CountDownLatch            :一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。 
CyclicBarrier             :一個同步輔助類,它允許一組線程互相等待,直到到達某個公共屏障點 
Future                    :Future 表示非同步計算的結果。
ScheduledExecutorService :一個 ExecutorService,可安排在給定的延遲後運行或定期執行的命令。
接下來逐一介紹
Executors主要方法說明
newFixedThreadPool(固定大小線程池)
創建一個可重用固定線程集合的線程池,以共用的無界隊列方式來運行這些線程(只有要請求的過來,就會在一個隊列里等待執行)。如果在關閉前的執行期間由於失敗而導致任何線程終止,那麼一個新線程將代替它執行後續的任務(如果需要)。
newCachedThreadPool(無界線程池,可以進行自動線程回收)
創建一個可根據需要創建新線程的線程池,但是在以前構造的線程可用時將重用它們。對於執行很多短期非同步任務的程式而言,這些線程池通常可提高程式性能。調用 execute 將重用以前構造的線程(如果線程可用)。如果現有線程沒有可用的,則創建一個新線程並添加到池中。終止並從緩存中移除那些已有 60 秒鐘未被使用的線程。因此,長時間保持空閑的線程池不會使用任何資源。註意,可以使用 ThreadPoolExecutor 構造方法創建具有類似屬性但細節不同(例如超時參數)的線程池。
newSingleThreadExecutor(單個後臺線程)
創建一個使用單個 worker 線程的 Executor,以無界隊列方式來運行該線程。(註意,如果因為在關閉前的執行期間出現失敗而終止了此單個線程,那麼如果需要,一個新線程將代替它執行後續的任務)。可保證順序地執行各個任務,並且在任意給定的時間不會有多個線程是活動的。與其他等效的 newFixedThreadPool(1) 不同,可保證無需重新配置此方法所返回的執行程式即可使用其他的線程。
這些方法返回的都是ExecutorService對象,這個對象可以理解為就是一個線程池。
這個線程池的功能還是比較完善的。可以提交任務submit()可以結束線程池shutdown()。

 
01    import java.util.concurrent.ExecutorService;
02    import java.util.concurrent.Executors;
03    public class MyExecutor extends Thread {
04    private int index;
05    public MyExecutor(int i){
06        this.index=i;
07    }
08    public void run(){
09        try{
10         System.out.println("["+this.index+"] start....");
11         Thread.sleep((int)(Math.random()*1000));
12         System.out.println("["+this.index+"] end.");
13        }
14        catch(Exception e){
15         e.printStackTrace();
16        }
17    }
18    public static void main(String args[]){
19        ExecutorService service=Executors.newFixedThreadPool(4);
20        for(int i=0;i<10;i++){
21         service.execute(new MyExecutor(i));
22         //service.submit(new MyExecutor(i));
23        }
24        System.out.println("submit finish");
25        service.shutdown();
26    }
27    }


雖然列印了一些信息,但是看的不是非常清晰,這個線程池是如何工作的,我們來將休眠的時間調長10倍。
Thread.sleep((int)(Math.random()*10000));
再來看,會清楚看到只能執行4個線程。當執行完一個線程後,才會又執行一個新的線程,也就是說,我們將所有的線程提交後,線程池會等待執行完最後shutdown。我們也會發現,提交的線程被放到一個“無界隊列里”。這是一個有序隊列(BlockingQueue,這個下麵會說到)。
另外它使用了Executors的靜態函數生成一個固定的線程池,顧名思義,線程池的線程是不會釋放的,即使它是Idle。
這就會產生性能問題,比如如果線程池的大小為200,當全部使用完畢後,所有的線程會繼續留在池中,相應的記憶體和線程切換(while(true)+sleep迴圈)都會增加。
如果要避免這個問題,就必須直接使用ThreadPoolExecutor()來構造。可以像通用的線程池一樣設置“最大線程數”、“最小線程數”和“空閑線程keepAlive的時間”。

這個就是線程池基本用法。
Semaphore
一個計數信號量。從概念上講,信號量維護了一個許可集合。如有必要,在許可可用前會阻塞每一個 acquire(),然後再獲取該許可。每個 release() 添加一個許可,從而可能釋放一個正在阻塞的獲取者。但是,不使用實際的許可對象,Semaphore 只對可用許可的號碼進行計數,並採取相應的行動。
Semaphore 通常用於限制可以訪問某些資源(物理或邏輯的)的線程數目。例如,下麵的類使用信號量控制對內容池的訪問:
這裡是一個實際的情況,大家排隊上廁所,廁所只有兩個位置,來了10個人需要排隊。

01    import java.util.concurrent.ExecutorService;
02    import java.util.concurrent.Executors;
03    import java.util.concurrent.Semaphore;
04    public class MySemaphore extends Thread {
05    Semaphore position;
06    private int id;
07    public MySemaphore(int i,Semaphore s){
08        this.id=i;
09        this.position=s;
10    }
11    public void run(){
12        try{
13         if(position.availablePermits()>0){
14          System.out.println("顧客["+this.id+"]進入廁所,有空位");
15         }
16         else{
17          System.out.println("顧客["+this.id+"]進入廁所,沒空位,排隊");
18         }
19         position.acquire();
20         System.out.println("顧客["+this.id+"]獲得坑位");
21         Thread.sleep((int)(Math.random()*1000));
22         System.out.println("顧客["+this.id+"]使用完畢");
23         position.release();
24        }
25        catch(Exception e){
26         e.printStackTrace();
27        }
28    }
29    public static void main(String args[]){
30        ExecutorService list=Executors.newCachedThreadPool();
31        Semaphore position=new Semaphore(2);
32        for(int i=0;i<10;i++){
33         list.submit(new MySemaphore(i+1,position));
34        }
35        list.shutdown();
36        position.acquireUninterruptibly(2);
37        System.out.println("使用完畢,需要清掃了");
38        position.release(2);
39    }
40    }

 

ReentrantLock
一個可重入的互斥鎖定 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監視器鎖定相同的一些基本行為和語義,但功能更強大。
ReentrantLock 將由最近成功獲得鎖定,並且還沒有釋放該鎖定的線程所擁有。當鎖定沒有被另一個線程所擁有時,調用 lock 的線程將成功獲取該鎖定並返回。如果當前線程已經擁有該鎖定,此方法將立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法來檢查此情況是否發生。
此類的構造方法接受一個可選的公平參數。
當設置為 true時,在多個線程的爭用下,這些鎖定傾向於將訪問權授予等待時間最長的線程。否則此鎖定將無法保證任何特定訪問順序。
與採用預設設置(使用不公平鎖定)相比,使用公平鎖定的程式在許多線程訪問時表現為很低的總體吞吐量(即速度很慢,常常極其慢),但是在獲得鎖定和保證鎖定分配的均衡性時差異較小。不過要註意的是,公平鎖定不能保證線程調度的公平性。因此,使用公平鎖定的眾多線程中的一員可能獲得多倍的成功機會,這種情況發生在其他活動線程沒有被處理並且目前並未持有鎖定時。還要註意的是,未定時的 tryLock 方法並沒有使用公平設置。因為即使其他線程正在等待,只要該鎖定是可用的,此方法就可以獲得成功。
建議總是 立即實踐,使用 try 塊來調用 lock,在之前/之後的構造中,最典型的代碼如下: 

01    class X {
02        private final ReentrantLock lock = new ReentrantLock();
03        // ...
04        public void m() {
05          lock.lock(); // block until condition holds
06          try {
07            // ... method body
08          } finally {
09            lock.unlock()
10          }
11        }
12    }
我的例子:

01    import java.util.concurrent.ExecutorService;
02    import java.util.concurrent.Executors;
03    import java.util.concurrent.locks.ReentrantLock;
04    public class MyReentrantLock extends Thread{
05    TestReentrantLock lock;
06    private int id;
07    public MyReentrantLock(int i,TestReentrantLock test){
08        this.id=i;
09        this.lock=test;
10    }
11    public void run(){
12        lock.print(id);
13    }
14    public static void main(String args[]){
15        ExecutorService service=Executors.newCachedThreadPool();
16        TestReentrantLock lock=new TestReentrantLock();
17        for(int i=0;i<10;i++){
18         service.submit(new MyReentrantLock(i,lock));
19        }
20        service.shutdown();
21    }
22    }
23    class TestReentrantLock{
24    private ReentrantLock lock=new ReentrantLock();
25    public void print(int str){
26        try{
27         lock.lock();
28         System.out.println(str+"獲得");
29         Thread.sleep((int)(Math.random()*1000));
30        }
31        catch(Exception e){
32         e.printStackTrace();
33        }
34        finally{
35         System.out.println(str+"釋放");
36         lock.unlock();
37        }
38    }
39    }

 

BlockingQueue
支持兩個附加操作的 Queue,這兩個操作是:檢索元素時等待隊列變為非空,以及存儲元素時等待空間變得可用。
BlockingQueue 不接受 null 元素。試圖 add、put 或 offer 一個 null 元素時,某些實現會拋出 NullPointerException。null 被用作指示 poll 操作失敗的警戒值。
BlockingQueue 可以是限定容量的。它在任意給定時間都可以有一個 remainingCapacity,超出此容量,便無法無阻塞地 put 額外的元素。
沒有任何內部容量約束的 BlockingQueue 總是報告 Integer.MAX_VALUE 的剩餘容量。
BlockingQueue 實現主要用於生產者-使用者隊列,但它另外還支持 Collection 介面。因此,舉例來說,使用 remove(x) 從隊列中移除任意一個元素是有可能的。
然而,這種操作通常不 會有效執行,只能有計劃地偶爾使用,比如在取消排隊信息時。
BlockingQueue 實現是線程安全的。所有排隊方法都可以使用內部鎖定或其他形式的併發控制來自動達到它們的目的。
然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)沒有 必要自動執行,除非在實現中特別說明。
因此,舉例來說,在只添加了 c 中的一些元素後,addAll(c) 有可能失敗(拋出一個異常)。
BlockingQueue 實質上不 支持使用任何一種“close”或“shutdown”操作來指示不再添加任何項。
這種功能的需求和使用有依賴於實現的傾向。例如,一種常用的策略是:對於生產者,插入特殊的 end-of-stream 或 poison 對象,並根據使用者獲取這些對象的時間來對它們進行解釋。
下麵的例子演示了這個阻塞隊列的基本功能。

01    import java.util.concurrent.BlockingQueue;
02    import java.util.concurrent.ExecutorService;
03    import java.util.concurrent.Executors;
04    import java.util.concurrent.LinkedBlockingQueue;
05    public class MyBlockingQueue extends Thread {
06    public static BlockingQueue<String> queue = new LinkedBlockingQueue<String>(3);
07    private int index;
08    public MyBlockingQueue(int i) {
09       this.index = i;
10    }
11    public void run() {
12       try {
13        queue.put(String.valueOf(this.index));
14        System.out.println("{" + this.index + "} in queue!");
15       } catch (Exception e) {
16        e.printStackTrace();
17       }
18    }
19    public static void main(String args[]) {
20       ExecutorService service = Executors.newCachedThreadPool();
21       for (int i = 0; i < 10; i++) {
22        service.submit(new MyBlockingQueue(i));
23       }
24       Thread thread = new Thread() {
25        public void run() {
26         try {
27          while (true) {
28           Thread.sleep((int) (Math.random() * 1000));
29           if(MyBlockingQueue.queue.isEmpty())
30            break;
31           String str = MyBlockingQueue.queue.take();
32           System.out.println(str + " has take!");
33          }
34         } catch (Exception e) {
35          e.printStackTrace();
36         }
37        }
38       };
39       service.submit(thread);
40       service.shutdown();
41    }
42    }

 

---------------------執行結果-----------------
{0} in queue!
{1} in queue!
{2} in queue!
{3} in queue!
0 has take!
{4} in queue!
1 has take!
{6} in queue!
2 has take!
{7} in queue!
3 has take!
{8} in queue!
4 has take!
{5} in queue!
6 has take!
{9} in queue!
7 has take!
8 has take!
5 has take!
9 has take!
-----------------------------------------

CompletionService
將生產新的非同步任務與使用已完成任務的結果分離開來的服務。生產者 submit 執行的任務。使用者 take 已完成的任務,
並按照完成這些任務的順序處理它們的結果。例如,CompletionService 可以用來管理非同步 IO ,執行讀操作的任務作為程式或系統的一部分提交,
然後,當完成讀操作時,會在程式的不同部分執行其他操作,執行操作的順序可能與所請求的順序不同。
通常,CompletionService 依賴於一個單獨的 Executor 來實際執行任務,在這種情況下,
CompletionService 只管理一個內部完成隊列。ExecutorCompletionService 類提供了此方法的一個實現。

01    import java.util.concurrent.Callable;
02    import java.util.concurrent.CompletionService;
03    import java.util.concurrent.ExecutorCompletionService;
04    import java.util.concurrent.ExecutorService;
05    import java.util.concurrent.Executors;
06    public class MyCompletionService implements Callable<String> {
07    private int id;
08     
09    public MyCompletionService(int i){
10       this.id=i;
11    }
12    public static void main(String[] args) throws Exception{
13       ExecutorService service=Executors.newCachedThreadPool();
14       CompletionService<String> completion=new ExecutorCompletionService<String>(service);
15       for(int i=0;i<10;i++){
16        completion.submit(new MyCompletionService(i));
17       }
18       for(int i=0;i<10;i++){
19        System.out.println(completion.take().get());
20       }
21       service.shutdown();
22    }
23    public String call() throws Exception {
24       Integer time=(int)(Math.random()*1000);
25       try{
26        System.out.println(this.id+" start");
27        Thread.sleep(time);
28        System.out.println(this.id+" end");
29       }
30       catch(Exception e){
31        e.printStackTrace();
32       }
33       return this.id+":"+time;
34    }
35    }

 

CountDownLatch

一個同步輔助類,在完成一組正在其他線程中執行的操作之前,它允許一個或多個線程一直等待。
用給定的計數 初始化 CountDownLatch。由於調用了 countDown() 方法,所以在當前計數到達零之前,await 方法會一直受阻塞。
之後,會釋放所有等待的線程,await 的所有後續調用都將立即返回。這種現象只出現一次——計數無法被重置。如果需要重置計數,請考慮使用 CyclicBarrier。
CountDownLatch 是一個通用同步工具,它有很多用途。將計數 1 初始化的 CountDownLatch 用作一個簡單的開/關鎖存器,
或入口:在通過調用 countDown() 的線程打開入口前,所有調用 await 的線程都一直在入口處等待。
用 N 初始化的 CountDownLatch 可以使一個線程在 N 個線程完成某項操作之前一直等待,或者使其在某項操作完成 N 次之前一直等待。
CountDownLatch 的一個有用特性是,它不要求調用 countDown 方法的線程等到計數到達零時才繼續,
而在所有線程都能通過之前,它只是阻止任何線程繼續通過一個 await。 
一下的例子是別人寫的,非常形象。

01    import java.util.concurrent.CountDownLatch;
02    import java.util.concurrent.ExecutorService;
03    import java.util.concurrent.Executors;
04    public class TestCountDownLatch {
05    public static void main(String[] args) throws InterruptedException {
06       // 開始的倒數鎖
07       final CountDownLatch begin = new CountDownLatch(1);
08       // 結束的倒數鎖
09       final CountDownLatch end = new CountDownLatch(10);
10       // 十名選手
11       final ExecutorService exec = Executors.newFixedThreadPool(10);
12       
13       for (int index = 0; index < 10; index++) {
14        final int NO = index + 1;
15        Runnable run = new Runnable() {
16         public void run() {
17          try {
18           begin.await();//一直阻塞
19           Thread.sleep((long) (Math.random() * 10000));
20           System.out.println("No." + NO + " arrived");
21          } catch (InterruptedException e) {
22          } finally {
23           end.countDown();
24          }
25         }
26        };
27        exec.submit(run);
28       }
29       System.out.println("Game Start");
30       begin.countDown();
31       end.await();
32       System.out.println("Game Over");
33       exec.shutdown();
34    }
35    }

 

CountDownLatch最重要的方法是countDown()和await(),前者主要是倒數一次,後者是等待倒數到0,如果沒有到達0,就只有阻塞等待了。


CyclicBarrier
一個同步輔助類,它允許一組線程互相等待,直到到達某個公共屏障點 (common barrier point)。
在涉及一組固定大小的線程的程式中,這些線程必須不時地互相等待,此時 CyclicBarrier 很有用。因為該 barrier 在釋放等待線程後可以重用,所以稱它為迴圈 的 barrier。
CyclicBarrier 支持一個可選的 Runnable 命令,在一組線程中的最後一個線程到達之後(但在釋放所有線程之前),
該命令只在每個屏障點運行一次。若在繼續所有參與線程之前更新共用狀態,此屏障操作 很有用。
示例用法:下麵是一個在並行分解設計中使用 barrier 的例子,很經典的旅行團例子:

 
01    import java.text.SimpleDateFormat;
02    import java.util.Date;
03    import java.util.concurrent.BrokenBarrierException;
04    import java.util.concurrent.CyclicBarrier;
05    import java.util.concurrent.ExecutorService;
06    import java.util.concurrent.Executors;
07    public class TestCyclicBarrier {
08      // 徒步需要的時間: Shenzhen, Guangzhou, Shaoguan, Changsha, Wuhan
09      private static int[] timeWalk = { 5, 8, 15, 15, 10 };
10      // 自駕游
11      private static int[] timeSelf = { 1, 3, 4, 4, 5 };
12      // 旅游大巴
13      private static int[] timeBus = { 2, 4, 6, 6, 7 };
14       
15      static String now() {
16         SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");
17         return sdf.format(new Date()) + ": ";
18      }
19      static class Tour implements Runnable {
20         private int[] times;
21         private CyclicBarrier barrier;
22         private String tourName;
23         public Tour(CyclicBarrier barrier, String tourName, int[] times) {
24           this.times = times;
25           this.tourName = tourName;
26           this.barrier = barrier;
27         }
28         public void run() {
29           try {
30             Thread.sleep(times[0] * 1000);
31             System.out.println(now() + tourName + " Reached Shenzhen");
32             barrier.await();
33             Thread.sleep(times[1] * 1000);
34             System.out.println(now() + tourName + " Reached Guangzhou");
35             barrier.await();
36             Thread.sleep(times[2] * 1000);
37             System.out.println(now() + tourName + " Reached Shaoguan");
38             barrier.await();
39             Thread.sleep(times[3] * 1000);
40             System.out.println(now() + tourName + " Reached Changsha");
41             barrier.await();
42             Thread.sleep(times[4] * 1000);
43             System.out.println(now() + tourName + " Reached Wuhan");
44             barrier.await();
45           } catch (InterruptedException e) {
46           } catch (BrokenBarrierException e) {
47           }
48         }
49      }
50      public static void main(String[] args) {
51         // 三個旅行團
52         CyclicBarrier barrier = new CyclicBarrier(3);
53         ExecutorService exec = Executors.newFixedThreadPool(3);
54         exec.submit(new Tour(barrier, "WalkTour", timeWalk));
55         exec.submit(new Tour(barrier, "SelfTour", timeSelf));
56    //當我們把下麵的這段代碼註釋後,會發現,程式阻塞了,無法繼續運行下去。
57         exec.submit(new Tour(barrier, "BusTour", timeBus));
58         exec.shutdown();
59      }
60    }

 



CyclicBarrier最重要的屬性就是參與者個數,另外最要方法是await()。當所有線程都調用了await()後,就表示這些線程都可以繼續執行,否則就會等待。
Future
Future 表示非同步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,並檢索計算的結果。
計算完成後只能使用 get 方法來檢索結果,如有必要,計算完成前可以阻塞此方法。取消則由 cancel 方法來執行。
還提供了其他方法,以確定任務是正常完成還是被取消了。一旦計算完成,就不能再取消計算。
如果為了可取消性而使用 Future但又不提供可用的結果,則可以聲明 Future<?> 形式類型、並返回 null 作為基礎任務的結果。
這個我們在前面CompletionService已經看到了,這個Future的功能,而且這個可以在提交線程的時候被指定為一個返回對象的。

ScheduledExecutorService
一個 ExecutorService,可安排在給定的延遲後運行或定期執行的命令。
schedule 方法使用各種延遲創建任務,並返回一個可用於取消或檢查執行的任務對象。scheduleAtFixedRate 和 scheduleWithFixedDelay 方法創建並執行某些在取消前一直定期運行的任務。
用 Executor.execute(java.lang.Runnable) 和 ExecutorService 的 submit 方法所提交的命令,通過所請求的 0 延遲進行安排。
schedule 方法中允許出現 0 和負數延遲(但不是周期),並將這些視為一種立即執行的請求。
所有的 schedule 方法都接受相對 延遲和周期作為參數,而不是絕對的時間或日期。將以 Date 所表示的絕對時間轉換成要求的形式很容易。
例如,要安排在某個以後的日期運行,可以使用:schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)。
但是要註意,由於網路時間同步協議、時鐘漂移或其他因素的存在,因此相對延遲的期滿日期不必與啟用任務的當前 Date 相符。
Executors 類為此包中所提供的 ScheduledExecutorService 實現提供了便捷的工廠方法。
一下的例子也是網上比較流行的。

01    import static java.util.concurrent.TimeUnit.SECONDS;
02    import java.util.Date;
03    import java.util.concurrent.Executors;
04    import java.util.concurrent.ScheduledExecutorService;
05 

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

-Advertisement-
Play Games
更多相關文章
  • Spring的另一個重要思想是AOP,面向切麵的編程,它提供了一種機制,可以在執行業務前後執行另外的代碼,Servlet中的Filter就是一種AOP思想的體現,下麵通過一個例子來感受一下. 假設我們現在需要在針對資料庫進行CRUD操作時添加一組日誌,即在執行CRUD方法前後分別加上一句話,實現簡單
  •  
  • 一個基於redis的處理session的方法,如下。 補充: php.ini文件中的session.gc_probability與session.gc_divisor兩個配置選項共同決定gc函數調用的時機。預設值分為為1和1000,表示每個請求只有1/1000的機會調用gc函數。
  •     問題的產生,必有其理由。說白點也就是客戶需要,沒辦法的事。不過也到給我們添了不少麻煩。本人也希望大牛們能給在下提提更多的思路,在下在此謝過。     具體是這樣:   1.要記錄操作人員,操作時間,操作相應模塊          2.要記錄操作的原始數據(ps:列級別)和變更後數據    
  • 前言:OpenCV對圖像及視頻的處理方便且很專業,對於攝像頭的支持也很好,但有個不足就是它雖然具有GUI模塊(即highgui),但是實在是很簡陋,就連一個按鍵都無法直接實現(需要藉助滾動條實現),這一點難以滿足可視化的圖像處理的想法;另一方面,Qt作為一個優秀的圖形庫,在GUI上表現出色,且界面設
  •                                                   
  • 瀏覽器和伺服器之間是通過 HTTP 協議進行連接通訊的。這是一種基於請求和響應模型的協議。瀏覽器通過 URL 向伺服器發起請求,Web 伺服器接收到請求,執行一段程式,然後做出響應,發送相應的html代碼給客戶端。 這就有了一個問題,Web 伺服器執行一段程式,可能幾毫秒就完成,也可能幾分鐘都完不成
  • 1.&按位“與”的計算是把兩個數字分別寫成二進位形式,然後按照每一位進行比較,&計算中,只要有一個是0就算成02.|運算轉換成2進位進行比較,兩個位只要有一個為1,那麼結果就是1,否則就為03.^兩個數轉換為2進位然後比較位,相同則結果為0,不同則結果為1
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...