java-多線程與併發

来源:https://www.cnblogs.com/lyh1024/archive/2022/10/12/16786357.html
-Advertisement-
Play Games

java-多線程與併發 以下內容為本人的學習筆記,如需要轉載,請聲明原文鏈接 https://www.cnblogs.com/lyh1024/p/16786357.html 多線程 1.進程與線程 1.1 什麼是進程 程式是指令和數據的有序集合,其本身沒有任何運行的含義,是一個靜態的概念。而進程是程 ...


java-多線程與併發


 以下內容為本人的學習筆記,如需要轉載,請聲明原文鏈接 https://www.cnblogs.com/lyh1024/p/16786357.html


 

多線程

1.進程與線程

1.1 什麼是進程

程式是指令和數據的有序集合,其本身沒有任何運行的含義,是一個靜態的概念。而進程是程式在處理機上的一次執行過程,它是一個動態的概念。

進程是一個具有一定獨立功能的程式,一個實體,每一個進程都有它自己的地址空間

 

1.2 進程的狀態

進程執行時的間斷性,決定了進程可能有多種狀態。事實上,運行中的進程具有以下三種基本狀態。

  • 就緒狀態(Ready)

  • 運行狀態(Running)

  • 阻塞狀態(Blocked)

cpu是有轉速的,轉速越快,性能越高。但CPU運行a程式時,要轉到h程式,需要時間,這時候進程就會進入阻塞狀態。

 

1.3 線程

線程實際上是在進程基礎上調度進一步劃分,一個線程啟動後,裡面的若幹程式又可以劃分成若幹個線程。

線程:是進程中的一個執行路徑,共用一個記憶體空間,程式之間可以自由切換,併發執行,一個進程最少有一個線程(單線程程式)

一個程式可以同時執行多個任務,來提高效率

例如:①同時下載多部電影

②看電影的同時吃零食

並行:就是兩個任務同時運行(多個CPU)

併發:是指兩個任務同時請求運行,而處理器一次只能接受一個任務,就會把兩個任務安排輪流執行,由於CPU時間片運行時間較短,就會感覺是兩個任務在同時執行

  • 面試題:進程和線程的區別?

 

2.線程的基本使用

線程實現的三種方式

在java中如果要想實現多線程的操作,有兩種實現方法:

1)繼承Thread類(只能繼承一個)

2)實現Runnable介面(建議使用,可以實現多個)

3)實現Callable介面

public class ThreadDeom1{
    
    public static void main(String[] args){
        MyThread mt = new Thread();
        
        MyRunnable mr = new MyRunnable();
        Thread t2 = new Thread(mr);//將實現Runnable的當成一個任務放進線程Thread里
        
        mt.start();//啟動線程,(實際是,start表準備就緒,告訴虛擬機可以啟動線程)
        t2.start();//可簡寫,合併成:new Thread(mr).start
    }
}
​
/**
創建線程方式一:
1.繼承Thread類
2.重寫run()方法
3.創建線程對象,調用start()開啟線程
註意:線程開啟不一定立即執行,有CPU調度執行
*/
class MyThread extends Thread{
    
    public void run(){
        for(int i = 0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"-"+i);
            try{
                Thread.sleep(500);
             }catch (InterruptedException e){
                 e.printStackTrace();
            }
        }
    }
}
​
/**
創建線程方式二:
1.實現Runnable介面
2.重寫run()方法
3.創建Runnable介面的實現類對象
4.創建線程對象(代理),丟入實現類對象
5.調用start()開啟線程
註意:線程開啟不一定立即執行,有CPU調度執行
*/
class MyRunnable implement Runnable{
     public void run(){
        for(int i = 0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"-"+i);
            try{
            /**
                線程的休眠
                在當前線程的執行中,暫停指定的毫秒數,釋放CPU的時間片
                釋放CPU的時間片意思:CPU給一個進程P ,3秒執行時間,線程A和線程B互搶這個3秒時間片,設A搶到了,執行了3秒,此時會中斷,然後CPU執行下一個進程,CPU一直轉,又轉到進程P,然後A和B又開始互搶這3秒時間片,A又搶到,繼上次中斷的位置開始執行,執行完後,就讓時間片給其他線程。
            */
                Thread.sleep(500);
            
            }catch (InterruptedException e){
                e.printStackTrace();
                
            }
        }
    }
}
/**
創建線程方式三:
1.實現Callable介面,需要返回值類型
2.重寫call方法,需要拋出異常
3.創建目標對象
4.創建執行服務:ExecutorService ser = Exectors.newFixedThreadPool(1);
5.提交執行:Future<Boolean> result1 = ser.submit(t1);
6.獲取結果:boolean r1 = result1.get();
7.關閉服務:ser.shutdownNow();
*/

案例

 

3.線程的休眠

public static void sleep(long millis) throws InterruptedException

使當前正在執行的線程以指定的毫秒數暫停(暫時停止執行),釋放CPU的時間片,具體取決於系統定時器和調度程式的精度和準確性。 線程不會丟失任何顯示器的所有權。

  • 參數

    millis - 以毫秒為單位的睡眠時間長度

  • 異常

    IllegalArgumentException - 如果 millis值為負數

    InterruptedException - 如果任何線程中斷當前線程。 當拋出此異常時,當前線程的中斷狀態將被清除。 由線程本身中斷不會報錯。

  • 線程的休眠,目的是讓出CPU執行的時間片,讓其他工作的線程可以執行,但不會釋放對象鎖

  • sleep可以模擬網路延遲,倒計時等

  • sleep時間達到後線程進入就緒狀態

public static void sleep(long millis,int nanos) throws InterruptedException//毫秒,納秒
public static Thread currentThread()//返回對當前正在執行的線程對象的引用,即獲取當前線程

 

4.join與中斷線程

public final void join() throws InterruptedException

等待這個線程死亡。

調用此方法的行為方式與調用join(0) 完全相同

  • 異常

    InterruptedException - 如果任何線程中斷當前線程。 當拋出此異常時,當前線程的中斷狀態將被清除。

   
public void interrupt() 中斷這個線程。 除非當前線程中斷自身,這是始終允許的
public static boolean interrupted() 測試當前線程是否中斷。 該方法可以清除線程的中斷狀態 。 換句話說,如果這個方法被連續調用兩次,那麼第二個調用將返回false(除非當前線程再次中斷,在第一個調用已經清除其中斷狀態之後,在第二個調用之前已經檢查過)。 忽略線程中斷,因為線程在中斷時不存在將被該方法返回false所反映。

join方法的主要作用就是同步,它可以使得線程之間的並行執行變為串列執行

或者說 join合併線程,待此線程執行完畢後,回去再執行其他線程,其他線程阻塞,可以想象成插隊

public class ThreadDeom2{
    public static void main(String[] args){
        MyRunnable mr2 = new MyRunnable2();
        Thread t1 = new Thread(mr2);
        MyRunnable mr3 = new MyRunnable3();
​
        Thread t2 = new Thread(mr3);
        t1.start();
        t2.start();
        for(int i = 0;i<50;i++){
            System.out.println(Thread.currentThread().getName()+"-"+i);
            try{
            
                 Thread.sleep(300);
            
             }catch (InterruptedException e){
                e.printStackTrace();
                }
            if(i==20){
             /**  try{          
                    t1.join();//讓t線程執行完畢
                }catch (InterruptedException e){
                     e.printStackTrace();
                    }*/
                
              //  t1.interrupt();//中斷線程(不會真的中斷線程),只是作了一個中斷標記
                
               mr3.flag =false; 
             }
         }
    }
}
​
class MyRunable2 implements Runnable{
    public void run(){
        for(int i = 0;i<50;i++){
            /**
            中斷線程方式一:
            1.使用interrupt()來中斷線程,設置一個中斷狀態(標記)
            2.run方法里測試中斷狀態Thread.interrupted()
            3.有sleep等拋出InterruptedException異常的,要重新打上中斷標記
            
            中斷線程方式二(更加推薦使用):
            1.自定義標記的方式,設置一個布爾值為ture,作為while的條件,要中斷時就將該布爾值設為false
            */
            if(Thread.interrupted()){//測試中斷狀態,此方法會把中斷狀態清除
                break;//不會真的中斷,因為sleep會拋出InterruptedException異常並把中斷標記清除,所以要重新打上中斷標記
            }
            System.out.println(Thread.currentThread().getName()+"-"+i);
            try{
            
                 Thread.sleep(300);
            
             }catch (InterruptedException e){
                e.printStackTrace();
                Thread.currentThread().interrupt();//重新打上中斷標記
                }
         }
    }
}
​
class MyRunable3 implements Runnable{
    
    public boolean flag = true;
    
    public MyRunable3(){
        flag = true;
    }
    
    public void run(){
        int i = 0;
        while(flag){
             System.out.println(Thread.currentThread().getName()+"---"+i);
            try{
            
                 Thread.sleep(300);
            
             }catch (InterruptedException e){
                e.printStackTrace();
                }
        }
    }
}

 

5.守護線程與yield

線程分為用戶線程和守護線程

虛擬機必須確保用戶線程執行完畢

虛擬機不用等待守護線程執行完畢(用戶線程執行完畢後,JVM虛擬機自動退出,不用等待守護線程執行完畢)

如,後臺記錄操作日誌,監控記憶體,垃圾回收等待....

method 說明
public final void setDaemon(boolean on) 將此線程標記為daemon線程或用戶線程。 當運行的唯一線程都是守護進程線程時,Java虛擬機將退出。
public final boolean isDaemon() 測試這個線程是否是守護線程。
public static void yield() 暫停當前正在執行的線程對象,並執行其他線程(瞭解)

yield作用是暫停當前正在執行的線程對象(放棄當前CPU資源),並執行其他線程。

yield是讓當前運行線程回到可運行狀態,以允許具有相同優先順序的其他線程獲得運行機會

補充:當源碼由native修飾,表示為本地方法,由c或c++實現

        for(int i = 0;i<50;i++){
            System.out.println(Thread.currentThread().getName()+"-"+i);
            try{
            
                 Thread.sleep(300);
            
             }catch (InterruptedException e){
                e.printStackTrace();
                }
            if(i==5){
                Thread.yield();//讓出本次CPU執行時間片,就讓一次,下一次還搶CPU時間片
            }
         }

 

6.其他方法與優先順序

Method or Fieids 說明
long getId() 返回此線程的標識符。
String getName() 返回此線程的名稱。
int getPriority() 返回此線程的優先順序。
boolean isAlive() 測試這個線程是否處於活動狀態,start以後就是活動狀態。
void setName(String name) 將此線程的名稱更改為等於參數 name
void``setPriority(int newPriority) 更改此線程的優先順序。
static int MAX_PRIORITY 線程可以擁有的最大優先順序。
static int ``MIN_PRIORITY 線程可以擁有的最小優先順序。
static int NORM_PRIORITY 分配給線程的預設優先順序。

 

7.線程同步

7.1 多線程共用數據

在多線程的操作中,多個線程有可能同時處理同一個資源,這就是多線程中的共用數據

7.2線程同步

解決數據共用問題,必須使用同步,所謂同步就是指多個線程在同一個時間段內只有一個線程執行指定代碼,其他線程要等待此線程完成之後才可以繼續執行。

在多線程編程時,一些敏感數據不允許被多個線程同時訪問,此時就使用同步訪問技術,保證數據在任何時刻,最多有一個線程訪問,以保證數據的完整性

線程同步是確保多線程共用數據的安全性,同時也會犧牲性能,同步過多還可能產生死鎖,因此務必按需求使用同步

線程進行同步,有以下兩種方法:

1)同步代碼塊

synchronized(要同步的對象){

要同步的操作;

}

2)同步方法

public synchronized void method(){

要同步的操作;

}

3)Lock(ReentrantLock)

/**
1.多線程共用數據時,會發生線程不安全的情況(多個線程操作同一個數據)
2.多線程共用數據必須使用同步
*/
public class ThreadDeom4{
    public static void main(String[] args){
        MyRunnable5 = mr5 = new MyRunnable();//一個任務
        Thread t1 = new Thread(mr5);//線程1
        Thread t2 = new Thread(mr5);//線程2
        
        t1.start();
        t2.start();
​
    }
}
​
class MyRunnable5 implements Runnable{
    
    private int ticket;//售票
    Object obj = new Object;
    public void run(){
    
        for(int i = 0;i<300;i++){
            if(ticket>0){
                //同步方法一:
                synchronized(obj){
                     ticket--;
                try{
                    Thread.sleep(1000);
                 }catch (InterruptedException e){
                     e.printStackTrace();
                }
                System.out.println("您購買的票已剩餘"+ticket--+"張");
                }
               //method();
            }
        }
    }
    
    //同步方法二,同步的對象是當前對象(this)
     private synchronized void method(){
         if(ticket>0){
                     ticket--;
                try{
                    Thread.sleep(1000);
                 }catch (InterruptedException e){
                     e.printStackTrace();
                }
                System.out.println("您購買的票已剩餘"+ticket--+"張");
                }
     }
    
    //同步方法三,Lock鎖
    ReentrantLock lock = new ReentrantLock():
     private synchronized void method(){
         lock.lock();//上鎖
         try{
         if(ticket>0){
                     ticket--;
                try{
                    Thread.sleep(1000);
                 }catch (InterruptedException e){
                     e.printStackTrace();
                }
                System.out.println("您購買的票已剩餘"+ticket--+"張");
                }
         }finally{//加try,catch確保一定能釋放鎖,避免死鎖
         lock.unlock();//釋放鎖
         }
     }
}

解釋sleep不會釋放對象鎖:

sleep在synchronized同步代碼塊里,CPU分配3秒,任務執行1秒,sleep休眠1秒,剩下一秒sleep會釋放CPU時間片給其他線程,但是不會釋放對象鎖,也就是synchronized鎖還在,儘管有釋放CPU時間片,但是其他線程進不去這個同步鎖裡面,所以剩下1秒其他線程只能幹等。

7.3同步準則

當編寫synchronized塊時,有幾個簡單的準則可以遵循,這些準則在避免死鎖和性能危險的風險方面大有幫助:

  1. 使代碼塊保持簡潔。把不隨線程變化的預處理和後處理移出synchronized塊

  2. 不要阻塞。如InputStream.read()。

  3. 在持有鎖的時候,不要對其他對象調用其同步方法

 

8.死鎖

過多的同步有可能出現死鎖,死鎖的操作一般是在程式運行的時候才有可能出現

多線程中要進行資源的共用,就需要同步,但同步過多,就可能造成死鎖,要避免出現死鎖

線程死鎖:在一個同步方法中調用了另一個對象的同步方法,可能產生死鎖。

 

9.生產者和消費者問題

面試題;sleep與wait的區別?

  • sleep:讓線程進入休眠狀態,讓出CPU的時間片,不釋放對象鎖

  • wait:讓線程進入等待狀態,讓出CPU的時間片,並釋放對象鎖,等待其他線程通過notify方法來喚醒(同步方法里使用)

 

10.線程生命周期圖:

 

11、線程池

線程池是預先創建線程的一種技術。線程池在還沒有任務到來之前,創建一定數量的線程,放入空閑隊列中,然後對這些資源進行賦予。減少頻繁的創建和銷毀對象。

JDK1.5版本以上提供了現成的線程池

java裡面線程池的頂級介面是Executor,是一個執行線程的工具

線程池介面是ExecutorService

使用線程池的好處:

線程可以重覆利用,減少創建和銷毀線程所帶來的的系統資源的開銷,提升性能(節省線程創建的時間開銷,使程式響應更快)

 

java.util.concurrent包:併發編程中很常用的使用工具類(concurrent併發(工具類))

Executor介面(譯:執行器) :

執行已提交的Runnable任務的對象

ExecutorService介面:

Executor提供了管理終止的方法,以及可為跟蹤一個或多個非同步任務執行狀況而生成Future的方法

Executors類:

此包中所定義的Executor、ExecutorService等的工廠和使用方法

在Executors類裡面提供了一些靜態工廠,生成一些常用的線程池

newSingleThreadExecutor:

  創建一個單線程的線程池。這個線程只有一個線程在工作,也就是相當於單線程串列執行所有任務。如果這個唯一的線程因為異常結束,那麼會有一個新的線程來替代它。此線程池保證所有任務的執行順序按照任務的提交順序執行。

newFixedThreadPool: (fixed 譯:固定的)

  創建固定大小的線程池。每次提交一個任務就創建一個線程,直到線程達到線程池的最大大小。

線程池的大小一旦達到最大值就會保持不變,如果某個線程因為執行異常而結束,那麼線程池會補充一個新線程

newCachedThreadPool(少用到):

  創建一個可緩存的線程池。如果線程池的大小超過了處理任務所需要的線程,那麼就會回收部分空閑(60秒不執行任務)的線程,當任務數增加時,此線程池又可以智能的添加新線程來處理任務。此線程池不會對線程池大小做限制,線程池大小完全依賴於操作系統(或者說JVM)能夠創建的最大線程大小。

newScheduledThreadPool:

  創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求

public class ThreadDeom5{
    public static void main(String[] args){
        //創建線程池(4種)
        //1.創建一個單線程的線程池,每個線程輪流走完才輪到下一個走
    //  ExecutorService es = Executors.newSingleThreadExecutor();
        //2.創建固定大小的線程池,多個線程同時執行
    //  ExecutorService es = Executors.newFixedThreadPool(2);
        //3.創建一個可緩存的線程池
    //  ExecutorService es = Executors.newCacheThreadPool();
        //4.創建一個大小無限的線程池
    //  ExecutorService es = Executors.newScaheduleThreadPool(3)//要給初始容量
    /** es.execute(new MyRunnable6());//一個execute()就為一個線程,MyRunnable6是一個任務
        es.execute(new MyRunnable6());*/
        es.schedule(new MYRunnable6(),3000,TimeUnit.MiLLISECONDS);//可以調度:延遲3秒
        es.shutdown();
    }
}
​
class MyRunnable6 implements Runnable{
    public void run(){
        for(int i = 0;i<10;i++){
            System.out.println(Thread.currentThread().getName()+"-"+i);
        
            try{
                Thread.sleep(300);
             }catch (InterruptedException e){
                 e.printStackTrace();
            }
        }
    }
}

 

 

 

有待補充......

參考資料:

JDK1.8幫助文檔

 

2022-10-12  22:22:12

 

 

 

 

 


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

-Advertisement-
Play Games
更多相關文章
  • 就像我們經常所說的:沒有最好的架構,只有最合適的架構。一個好的架構師,可以根據具體的需求、所擁有的資源等因素綜合考慮而設計出最優的架構方案。特別是現在,業務的飛速變化、數據無處不在等這些因素的影響下,技術和框架也需要在變化的過程中不斷地打磨和提升以適應新的業務需要。可能當時是最好的架構,但是後來我們... ...
  • 關於架構師的成長之路,還存在著一個誤區,就是把架構師預設為軟體架構師。因為今天我們所遇到的架構師,大多數都是圍繞著軟體研發。事實上這個認識有一定的片面性。誠然,現今我們所構建的系統都是軟體系統,但是在實際的工作過程中,隨著信息技術在深度和廣度上的快速發展,除了軟體研發以外,測試、網路、安全、配置、系... ...
  • 享元設計模式(Flyweight Design Pattern)通過共用技術實現相同或相似對象的重用,節省記憶體,前提是享元對象是不可變對象。 ...
  • SynchronousQueue介紹 【1】SynchronousQueue是一個沒有數據緩衝的BlockingQueue,生產者線程對其的插入操作put必須等待消費者的移除操作take。 【2】如圖所示,SynchronousQueue 最大的不同之處在於,它的容量為 0,所以沒有一個地方來暫存元 ...
  • 1.數字的簡單運算 常用運算符 +, -, *, /, %, //,** = 就是賦值運算符,在變數介紹中已提及過,a=13; 這裡要說下賦值運算符的參數運算, +=, -=, *=, /=, //=, %= a += b --> a = a + b 參數賦值可以使代碼更整潔,可讀性更強 b,kb, ...
  • 前言 嗨嘍~大家好呀,這裡是魔王吶 ! 閑的無聊的得我又來倒騰代碼了~ 今天給大家分享得是——122萬人的生活工作和死亡數據分析 準備好了嘛~現在開始發車嘍!! @TOC 所需素材 獲取素材點擊 代碼 import pandas as pd df = pd.read_csv('.\data\AgeD ...
  • PriorityBlockingQueue介紹 【1】PriorityBlockingQueue是一個無界的基於數組的優先順序阻塞隊列,數組的預設長度是11,也可以指定數組的長度,且可以無限的擴充,直到資源消耗盡為止,每次出隊都返回優先順序別最高的或者最低的元素。預設情況下元素採用自然順序升序排序,當然 ...
  • 1、命名元組也叫具名元組 可以通過名稱來訪問序列中的元素,擺脫對位置的依賴,他本身是一個工廠函數 2、應用 實例化元組返回的類,其記憶體占用要比普通類實例要小的多,因為前者實例的屬性不通過字典進行管理 1 from collections import namedtuple 2 3 # 返回可實例化的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...