中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java多線程的概念及使用

發布時間:2021-07-29 13:41:07 來源:億速云 閱讀:145 作者:chen 欄目:開發技術

本篇內容介紹了“Java多線程的概念及使用”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

目錄
  • 線程

    • Thread類

    • Runnable接口創建線程

    • Thread和Runnable的區別

    • 匿名內部類方式實現線程的創建

  • 線程安全

    • 線程安全

    • 線程同步

    • 同步方法

    • Lock鎖

    • 線程狀態

  • 等待喚醒機制

    • 線程間通信

    • 等待喚醒機制

    • 生產者與消費者問題

  • 線程池

    • 線程池的概念

    • 線程池的使用


多線程

并發與并行

并發:指兩個或多個事件在同一個時間段內發生。
并行:指兩個或多個事件在同一時刻發生(同時發生)。

在操作系統中,安裝了多個程序,并發指的是在一段時間內宏觀上有多個程序同時運行,這在單 CPU系統中,每一時刻只能有一道程序執行,即微觀上這些程序是分時的交替運行,只不過是給人的感覺是同時運行,那是因為分時交替運行的時間是非常短的。

而在多個 CPU 系統中,則這些可以并發執行的程序便可以分配到多個處理器上(CPU),實現多任務并行執行, 即利用每個處理器來處理一個可以并發執行的程序,這樣多個程序便可以同時執行。目前電腦市場上說的多核CPU,便是多核處理器,核越多,并行處理的程序越多,能大大的提高電腦運行的效率。

線程與進程

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

我們可以再電腦底部任務欄,右鍵----->打開任務管理器,可以查看當前任務的進程:

Java多線程的概念及使用

線程調度:

分時調度: 所有線程輪流使用 CPU 的使用權,平均分配每個線程占用 CPU 的時間。
搶占式調度: 優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那么會隨機選擇一個(線程隨機性),Java使用的為搶占式調度。

創建線程類

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

定義Thread類的子類,并重寫該類的run()方法,該run()方法的方法體就代表了線程需要完成的任務,因此把 run()方法稱為線程執行體。創建Thread子類的實例,即創建了線程對象調用線程對象的start()方法來啟動該線程
public class Demo01 {
    public static void main(String[] args) {
        //創建自定義線程對象
        MyThread mt = new MyThread("新的線程!");
        //開啟新線程
        mt.start();
        //在主方法中執行for循環
        for (int i = 0; i < 10; i++) {
            System.out.println("main線程!"+i);
        }
    }
}
class MyThread extends Thread {
    //定義指定線程名稱的構造方法
    public MyThread(String name) {
        //調用父類的String參數的構造方法,指定線程的名稱 
        super(name);
    }
     //重寫run方法
    @Override
    public void run() {
        for (int i=0;i<100;i++)
        System.out.println(getName()+":正在執行!"+i);
    }
}

線程

Thread類

構造方法:

public Thread() :分配一個新的線程對象。
public Thread(String name) :分配一個指定名字的新的線程對象。
public Thread(Runnable target) :分配一個帶有指定目標新的線程對象。
public Thread(Runnable target,String name) :分配一個帶有指定目標新的線程對象并指定名字。

常用的方法:

public String getName() :獲取當前線程名稱。
public void start() :導致此線程開始執行; Java虛擬機調用此線程的run方法。
public void run() :此線程要執行的任務在此處定義代碼。
public static void sleep(long millis) :使當前正在執行的線程以指定的毫秒數暫停(暫時停止執行)。
public static Thread currentThread() :返回對當前正在執行的線程對象的引用。

Runnable接口創建線程

Runnable接口創建線程的步驟:

定義Runnable接口的實現類,并重寫該接口的run()方法,該run()方法的方法體同樣是該線程的線程執行體。創建Runnable實現類的實例,并以此實例作為Thread的target來創建Thread對象,該Thread對象才是真正的線程對象。調用線程對象的start()方法來啟動線程。

代碼案例:

public class Demo2 {
    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("李四 " + i);
        }
    }
}
class MyRunnable implements Runnable{
    @Override 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()方法來運行的。因此,不管是繼承Thread類還是實現Runnable接口來實現多線程,最終還是通過Thread的對象的API來控制線程的,熟悉Thread類的API是進行多線程編程的基礎。

Thread和Runnable的區別

如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable接口的話,則很容易的實現資源共享。

實現Runnable接口比繼承Thread類所具有的優勢:

適合多個相同的程序代碼的線程去共享同一個資源。可以避免java中的單繼承的局限性。增加程序的健壯性,實現解耦操作,代碼可以被多個線程共享,代碼和線程獨立。線程池只能放入實現Runable或Callable類線程,不能直接放入繼承Thread的類。

匿名內部類方式實現線程的創建

使用線程的內匿名內部類方式,可以方便的實現每個線程執行不同的線程任務操作。 使用匿名內部類的方式實現Runnable接口,重新Runnable接口中的run方法:

public class Demo3 {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 200; i++) {
                    System.out.println("張三 " + i);
                }
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 200; i++) {
                    System.out.println("李四 " + i);
                }
            }
        }.start();
    }
}

線程安全

線程安全

如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。程序每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。

案例:游樂園賣票

假設游樂園要賣1000張票,一共有3個賣票窗口(3個窗口一起賣1000張票),采用線程對象來模擬;需要票,Runnable接口子類來模擬。

public class Demo4 {
    public static void main(String[] args) {
        //創建線程任務對象
        Ticket ticket = new Ticket();
        //創建三個窗口對象 
        Thread t1 = new Thread(ticket, "窗口1"); 
        Thread t2 = new Thread(ticket, "窗口2"); 
        Thread t3 = new Thread(ticket, "窗口3"); 
        //同時賣票 
        t1.start(); 
        t2.start(); 
        t3.start();
    }
}
class Ticket implements Runnable {
    private int ticket = 1000;
    /** 執行賣票操作 */
    @Override
    public void run() {
        //每個窗口賣票的操作
        // 窗口 永遠開啟
        while (true) {
            if (ticket > 0) {
                //有票 可以賣 使用sleep模擬一下出票時間
                try {Thread.sleep(100);
                } catch (InterruptedException e) {
                    // TODO Auto‐generated catch block
                    e.printStackTrace();
                }
                //獲取當前線程對象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在賣:" + (ticket--));
            }
        }
    }
}

但是結果會出先重復的票和0與-1這樣的錯誤票

Java多線程的概念及使用

這種問題,幾個窗口(線程)票數不同步了,這種問題稱為線程不安全。

線程安全問題都是由全局變量及靜態變量引起的。若每個線程中對全局變量、靜態變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執行寫操作,一般都需要考慮線程同步, 否則的話就可能影響線程安全。

線程同步

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

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

賣票案例的線程同步簡述:

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

java完成線程同步的三種方式:

同步代碼塊
同步方法
鎖機制

同步代碼塊

同步代碼塊: **synchronized **關鍵字可以用于方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。

格式:

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

同步鎖:

同步鎖: 對象的同步鎖只是一個概念,可以想象為在對象上標記了一個鎖.。

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

同步代碼塊解決賣票案例:

public class Demo4 {
    public static void main(String[] args) {
        //創建線程任務對象
        Ticket ticket = new Ticket();
        //創建三個窗口對象
        Thread t1 = new Thread(ticket, "窗口1");
        Thread t2 = new Thread(ticket, "窗口2");
        Thread t3 = new Thread(ticket, "窗口3");
        //同時賣票
        t1.start();
        t2.start();
        t3.start();
    }
}
class Ticket implements Runnable {
    private int ticket = 100;
    /** 執行賣票操作 */
    Object obj=new Object();
    @Override
    public void run() {
        //每個窗口賣票的操作
        // 窗口 永遠開啟
        while (true) {
            synchronized (obj)
            {
                if (ticket > 0) {
                    //有票 可以賣 使用sleep模擬一下出票時間
                    try {Thread.sleep(500);
                    } 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 Demo5 {
    public static void main(String[] args) {
        //創建線程任務對象
        Ticket ticket = new Ticket();
        //創建三個窗口對象
        Thread t1 = new Thread(ticket, "窗口1");
        Thread t2 = new Thread(ticket, "窗口2");
        Thread t3 = new Thread(ticket, "窗口3");
        //同時賣票
        t1.start();
        t2.start();
        t3.start();
    }
}
class Ticket implements Runnable {
    private int ticket = 100;
    /**
     * 執行賣票操作
     */
    Object obj = new Object();
    @Override
    public void run() {
        while (true)
        {
            sellticket();
        }
    }
    public synchronized void sellticket() {
        synchronized (obj) {
            if (ticket > 0) {
                //有票 可以賣 使用sleep模擬一下出票時間
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    // TODO Auto‐generated catch block
                    e.printStackTrace();
                }
                //獲取當前線程對象的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在賣:" + (ticket--));
            }
        }
    }
}

Lock鎖

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

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

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo6 {
    public static void main(String[] args) {
        //創建線程任務對象
        Ticket ticket = new Ticket();
        //創建三個窗口對象
        Thread t1 = new Thread(ticket, "窗口1");
        Thread t2 = new Thread(ticket, "窗口2");
        Thread t3 = new Thread(ticket, "窗口3");
        //同時賣票
        t1.start();
        t2.start();
        t3.start();
    }
}
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();
        }
    }
}

線程狀態

當線程被創建并啟動以后,它既不是一啟動就進入了執行狀態,也不是一直處于執行狀態。在線程的生命周期中, 有幾種狀態呢?在API中 java.lang.Thread.State 這個枚舉中給出了六種線程狀態:

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

等待喚醒機制

線程間通信

概念:多個線程在處理同一個資源,但是處理的動作(線程的任務)卻不相同。

比如:線程A用來生成包子的,線程B用來吃包子的,包子可以理解為同一資源,線程A與線程B處理的動作,一個是生產,一個是消費,那么線程A與線程B之間就存在線程通信問題。

為什么要處理線程間通信:

多個線程并發執行時, 在默認情況下CPU是隨機切換線程的,當我們需要多個線程來共同完成一件任務,并且我們希望他們有規律的執行, 那么多線程之間需要一些協調通信,以此來幫我們達到多線程共同操作一份數據。

如何保證線程間通信有效利用資源:

多個線程在處理同一個資源,并且任務不同時,需要線程通信來幫助解決線程之間對同一個變量的使用或操作。 就是多個線程在操作同一份數據時, 避免對同一共享變量的爭奪。也就是我們需要通過一定的手段使各個線程能有效的利用資源。而這種手段即—— 等待喚醒機制。

等待喚醒機制

等待喚醒機制這是多個線程間的一種協作機制。談到線程我們經常想到的是線程間的競爭(race),比如去爭奪鎖,但這并不是故事的全部,線程間也會有協作機制。 就是在一個線程進行了規定操作后,就進入等待狀態(wait()), 等待其他線程執行完他們的指定代碼過后 再將 其喚醒(notify());在有多個線程進行等待時, 如果需要,可以使用 notifyAll()來喚醒所有的等待線程。

wait/notify 就是線程間的一種協作機制。

等待喚醒中的方法:

. wait:線程不再活動,不再參與調度,進入 wait set 中,因此不會浪費 CPU 資源,也不會去競爭鎖了,這時的線程狀態即是 WAITING。它還要等著別的線程執行一個特別的動作,也即是“通知(notify)”在這個對象上等待的線程從wait set 中釋放出來,重新進入到調度隊列(ready queue)中
notify:則選取所通知對象的 wait set 中的一個線程釋放;例如,餐館有空位置后,等候就餐最久的顧客最先入座。
notifyAll:則釋放所通知對象的 wait set 上的全部線程。

生產者與消費者問題

等待喚醒機制其實就是經典的“生產者與消費者”的問題。 就拿生產包子消費包子來說等待喚醒機制如何有效利用資源:
包子鋪線程生產包子,吃貨線程消費包子。當包子沒有時(包子狀態為false),吃貨線程等待,包子鋪線程生產包子 (即包子狀態為true),并通知吃貨線程(解除吃貨的等待狀態),因為已經有包子了,那么包子鋪線程進入等待狀態。 接下來,吃貨線程能否進一步執行則取決于鎖的獲取情況。如果吃貨獲取到鎖,那么就執行吃包子動作,包子吃完(包 子狀態為false),并通知包子鋪線程(解除包子鋪的等待狀態),吃貨線程進入等待。包子鋪線程能否進一步執行則取 決于鎖的獲取情況。

public class Demo7 {
    public static void main(String[] args) {
        baozi bz=new baozi();
        new baozipu(bz).start();
        new chihuo(bz).start();
    }
}
class baozi{
    String pi;
    String xian;
    boolean flag=false;
}
class baozipu extends Thread {
    private baozi bz;
    public baozipu(baozi bz) {
        this.bz=bz;
    }
    @Override
    public void run() {
        int count=0;
        while (true){
            synchronized(bz)
            {
                if(bz.flag)
                {
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                if(count%2==0){
                    //生產 大蝦餡包子
                    bz.pi = "薄皮";
                    bz.xian = "大蝦陷";
                }else{
                    //生產 冰皮 羊肉大蔥陷
                    bz.pi = "冰皮";
                    bz.xian = "羊肉大蔥陷";
                }
                count++;
                System.out.println("包子鋪正在生產:"+bz.pi+bz.xian+"包子");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                bz.flag=true;
                System.out.println("包子鋪已經生產好了:"+bz.pi+bz.xian+"包子,吃貨可以開始吃了");
                bz.notify();
            }
        }
    }
}
class chihuo extends Thread{
    private baozi bz;
    public chihuo(baozi bz)
    {
        this.bz=bz;
    }
    @Override
    public void run() {
        while (true)
        {
            synchronized (bz){
                if (!bz.flag)
                {
                    try {
                        bz.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("吃貨正在吃:"+bz.pi+bz.xian+"的包子");
                //吃貨吃完包子
                //修改包子的狀態為false沒有
                bz.flag = false;
                //吃貨喚醒包子鋪線程,生產包子
                bz.notify();
                System.out.println("吃貨已經把:"+bz.pi+bz.xian+"的包子吃完了,包子鋪開始生產包子");
                System.out.println("----------------------------------------------------");
            }
        }
    }
}

線程池

線程池的概念

線程池:其實就是一個容納多個線程的容器,其中的線程可以反復使用,省去了頻繁創建線程對象的操作, 無需反復創建線程而消耗過多資源。

合理利用線程池能夠帶來三個好處:

  • 降低資源消耗。減少了創建和銷毀線程的次數,每個工作線程都可以被重復利用,可執行多個任務。

  • 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。

  • 提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的內 存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最后死機)。

線程池的使用

Java里面線程池的頂級接口是 java.util.concurrent.Executor ,但是嚴格意義上講 Executor 并不是一個線程 池,而只是一個執行線程的工具。真正的線程池接口是 java.util.concurrent.ExecutorService

要配置一個線程池是比較復雜的,尤其是對于線程池的原理不是很清楚的情況下,很有可能配置的線程池不是較優 的,因此在 java.util.concurrent.Executors 線程工廠類里面提供了一些靜態工廠,生成一些常用的線程池。官 方建議使用Executors工程類來創建線程池對象。

Executors類中有個創建線程池的方法如下:

public static ExecutorService newFixedThreadPool(int nThreads) :返回線程池對象。(創建的是有界線 程池,也就是池中的線程個數可以指定最大數量)

獲取到了一個線程池ExecutorService 對象,那么怎么使用呢,在這里定義了一個使用線程池對象的方法如下:

public Future<?> submit(Runnable task) :獲取線程池中的某一個線程對象,并執行
Future接口:用來記錄線程任務執行完畢后產生的結果。線程池創建與使用。

使用線程池中線程對象的步驟:

創建線程池對象。創建Runnable接口子類對象。(task)提交Runnable接口子類對象。(take task)關閉線程池(一般不做)。

案例:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo8 {
    public static void main(String[] args) {
        ExecutorService es= Executors.newFixedThreadPool(3);
        MyRunable r=new MyRunable();
        es.submit(r);
        es.submit(r);
        es.submit(r);
        es.submit(r);
    }
}
class MyRunable implements Runnable{
    @Override
    public void run() {
        System.out.println("我是一個廚師,我去做飯了");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("廚師做好飯了:"+Thread.currentThread().getName());

    }
}

“Java多線程的概念及使用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

惠来县| 包头市| 双鸭山市| 云林县| 当阳市| 弥渡县| 浦东新区| 龙南县| 江北区| 泗水县| 长丰县| 莆田市| 高淳县| 拉萨市| 泰宁县| 大化| 石屏县| 三门峡市| 无锡市| 松潘县| 辽中县| 茂名市| 乌苏市| 宿州市| 巫溪县| 秦安县| 鸡泽县| 武夷山市| 大邑县| 西安市| 阳西县| 天全县| 金川县| 鄂州市| 盐城市| 辽宁省| 天津市| 峨山| 随州市| 依安县| 宝鸡市|