日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

【Java】深入理解Java线程

發布時間:2024/7/5 java 28 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Java】深入理解Java线程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

1 相關概念

并發:兩個或多個事件在同一時間段內發生【多個任務交替執行】
并行:兩個或多個事件在同一時刻發生【多個任務同時執行】
進程:進入內存的程序
內存:所有應用程序都要進入到內存中執行 臨時存儲RAM
線程:進程的一個執行單元,負責程序的執行
一個程序至少有一個進程,一個進程可以包含多個線程
CPU:中央處理器,對數據進行計算,指揮軟件和硬件
單線程:CPU在多個線程之間做高速的切換,輪流執行多個線程,效率低
多線程:多個線程在多個任務之間做高速的切換,速度是單線程的多倍,多個線程之間互不影響

線程調度

  • 分時調度:所有線程輪流使用CPU,平均分配每個線程占用CPU的時間
  • 搶占式調度:優先讓優先級高的線程使用CPU,如果優先級相同,則隨機選擇一個,Java中使用搶占式調度。

2 主線程

主線程:執行main方法的線程
主線程的過程:JVM執行main方法,main方法進入到棧內存,JVM會找操作系統開辟一條main方法的執行路徑,CPU根據路徑來執行main方法,這個路徑就是主線程。
單線程:執行從main方法開始自上而下依次執行

public class Person {private String name;public Person(String name) {this.name = name;}public void run(){for (int i = 0; i < 3; i++) {System.out.println(name + " " + i);}} } public class MainThread {public static void main(String[] args) {Person p1 = new Person("張三");p1.run();Person p2 = new Person("李四");p2.run();} }

3 創建多線程程序-法1

3.1 創建Thread類的子類

  • 創建Thread類的子類
  • 在子類中重寫run方法,設置線程任務
  • 創建子類對象
  • 調用start方法,開始新的線程,執行run方法
  • main壓棧執行后,在堆內存中創建線程子類對象,棧中保存對象地址。如果調用run方法,run方法壓棧執行,則是單線程處理。如果調用start方法,會開辟新的棧空間執行run方法。CPU可以選擇不同的棧空間。

    start使線程開始執行,JVM調用線程的run方法,兩個線程并發運行
    (main線程)<----->(創建新線程執行run)

    public class MyThread extends Thread {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("run " + i);}} } public class MyThreadTest {public static void main(String[] args) {MyThread mt = new MyThread();mt.start();for (int i = 0; i < 5; i++) {System.out.println("main " + i);}} }

    同優先級下,隨機搶占,誰搶到誰執行

    3.2 Thread類常用方法

    獲取線程名稱:getName()、Tread.currentTread()

    Tread t = new Tread(); sout(t.getName());//名稱 Tread t = Tread.currentTread(); sout(t);//名稱

    設置線程名稱:setName()、構造方法參數傳遞線程名稱

    public class MyThreadTest {public static void main(String[] args) {MyThread mt1 = new MyThread("張三");mt1.start();MyThread mt2 = new MyThread();mt2.setName("李四");mt2.start();} } public class MyThread extends Thread {public MyThread(String name) {super(name);public MyThread() {}@Overridepublic void run() {System.out.println(getName());} }

    線程休眠:sleep(long millis) 毫秒結束后程序繼續執行

    模擬秒表 示例:

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

    4 創建多線程程序-法2【推薦使用】

    4.1 創建Runnable實現類

  • 創建Runanable接口的實現類
  • 實現類中重寫run方法,設置線程任務
  • 創建實現類的對象
  • 創建Thread類對象,構造方法中傳遞Runnale的實現類對象
  • 調用Thread類中的start方法
  • public class RunnableImpl implements Runnable {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread()+" "+ i);}} } public class MyThreadTest {public static void main(String[] args) throws InterruptedException {RunnableImpl run = new RunnableImpl();Thread t = new Thread(run);t.start();for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread() + " " + i);}} }

    4.2 兩種實現方法的區別

    Runnable的優點

    • 避免了單繼承的局限性,類繼承了Thread類就不能繼承其他類了,實現Runnable接口還可以實現其他接口。
    • 增強了程序的擴展性,降低了程序的耦合性(解耦)。把設置線程任務開啟線程進行了分離。實現類中重寫run方法來設置線程任務,創建Thread類對象調用start來開啟新線程。想要什么任務,就傳遞什么實現類對象。

    5 匿名內部類創建線程

    匿名內部類:簡化代碼。
    把1.子類繼承父類 2.重寫父類 3.創建子類對象 —> 一步完成
    把1.實現實現類接口 2.重寫接口方法 3.創建實現類對象 —> 一步完成

    public class MyThreadTest {public static void main(String[] args) throws InterruptedException {//Thread法new Thread(){@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread() + " " + i);}}}.start();//Runnable法new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread() + " " + i);}}}).start();} }

    6 線程安全

    共享資源產生安全問題

    public class RunnableImpl implements Runnable {//共享票源private int tickets = 100;@Overridepublic void run() {//重復賣票while(true){if(tickets > 0){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("賣第"+tickets+"張票");tickets--;}}} } public class MyThreadTest {public static void main(String[] args) throws InterruptedException {RunnableImpl r = new RunnableImpl();Thread t1 = new Thread(r);Thread t2 = new Thread(r);Thread t3 = new Thread(r);t1.start();t2.start();t3.start();} }

    出現了重復的票

    窗口Thread-2在賣第100張票
    窗口Thread-0在賣第100張票
    窗口Thread-1在賣第100張票
    窗口Thread-1在賣第97張票
    窗口Thread-2在賣第97張票
    窗口Thread-0在賣第97張票
    窗口Thread-0在賣第94張票
    窗口Thread-2在賣第94張票

    出現了不存在的票

    窗口Thread-2在賣第0張票
    窗口Thread-1在賣第-1張票

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

    注意:訪問共享數據的時候,無論是否時區CPU執行權,其他線程只能等待,等當前線程完全結束后,其他線程再繼續。

    7 同步技術的原理

    使用了一個鎖對象,這個對象叫同步鎖,也叫對象監視器。多個線程一起搶奪CPU執行權,誰搶到了,誰執行run方法,遇到同步代碼塊。

    此時,搶到CPU的當前線程T0會檢查同步代碼塊是否有鎖對象,如果有,則獲取鎖對象,進入到同步中進行。

    另一進程T1搶到CPU后發現沒有鎖對象了,則進入阻塞狀態,等待鎖對象的歸還,直到上一進程T0執行完同步代碼塊才歸還鎖對象T1進程才可以獲取到鎖對象,進入到同步中執行。

    同步中的線程,沒有執行完畢不會釋放鎖,同步外的線程沒有鎖對象,無法進入同步代碼塊。同步保證了只有一個線程再同步中執行共享數據,保證安全,但犧牲了效率。

    7.1 同步方法

    定義同步方法解決線程安全問題

  • 把訪問了共享數據的代碼取出來,放到一個方法中
  • 在方法上添加synchronized
  • 同步方法也會鎖住方法內部,只讓一個線程執行,鎖對象是實現類對象new RunnableImpl(),也就是this。

    public class RunnableImpl implements Runnable {//共享票源private int tickets = 100;@Overridepublic void run() {while(true){sell();}}public synchronized void sell(){if(tickets > 0){try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}String name = Thread.currentThread().getName();System.out.println("窗口"+name+"在賣第"+tickets+"張票");tickets--;}} }

    7.2 靜態同步方法

    加static關鍵字,鎖對象不是this,this是創建對象之后產生的,static優先于對象的創建,靜態同步方法的鎖對象是本類的class屬性—>class文件對象(反射)

    RunnableImpl.class

    7.3 Lock鎖

    JDK1.5之后出現Lock接口,實現了synchronized方法和語句,可獲得更廣泛的鎖操作。

  • 在成員位置創建一個ReentrantLock對象
  • 在可能會出現安全問題的代碼前調用Lock接口的lock方法獲取鎖
  • 在可能會出現安全問題的代碼后調用Lock接口的unlock方法釋放鎖
  • public class RunnableImpl implements Runnable {//共享票源private static int tickets = 100;Lock lock = new ReentrantLock();@Overridepublic void run() {while(true){lock.lock();if(tickets > 0){try {Thread.sleep(10);String name = Thread.currentThread().getName();System.out.println("窗口"+name+"在賣第"+tickets+"張票");tickets--;} catch (InterruptedException e) {e.printStackTrace();}finally {lock.unlock();}}}} }

    8 線程狀態

    • 新建狀態:new
    • 運行狀態:runnable
    • 阻塞狀態:blocked
    • 死亡狀態:terminated
    • 休眠狀態:time_waiting【等待時間】
    • 永久等待:waiting【等待喚醒】

    new —> start() + CPU —> runnable
    new —> start() - CPU —> blocked

    runnable —> stop() / run(over) —> terminated
    runnable —> sleep / wait —> timed_waiting

    timed_waiting —> time over - CPU—> blocked
    timed_waiting —> time over + CPU—> runnable

    runnable —> Object.wait() —> waiting
    waiting —> Object.notify() + CPU —> runnable
    waiting —> Object.notify() - CPU —> blocked

    9 線程通信

    9.1 等待喚醒案例

  • 創建消費者線程:申請資源種類和數量,調用wait方法,放棄CPU,進入waiting狀態。
  • 創建生產者線程:產生資源之后,調用notify喚醒消費者。
  • 兩個線程必須被同步代碼塊包裹,確保只有一個在執行。
  • 同步的鎖對象必須唯一,只有鎖對象可以調用wait和notify方法。
  • public class WaitAndNotify {public static void main(String[] args) {//鎖對象Object obj = new Object();//消費者new Thread(){@Overridepublic void run() {while(true){synchronized (obj){System.out.println("申請資源");try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("拿到資源");System.out.println("------------");}}}.start();//生產者new Thread(){@Overridepublic void run() {while (true){try {System.out.println("準備資源");Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}synchronized (obj){obj.notify();System.out.println("資源已備好");}}}}.start();} }

    Object.wait(long m):無參數的wait需要等待notify喚醒,有參數的wait等到時間結束后,進入到runnable(有CPU)或者blocked(無CPU)狀態,相當于sleep(long m),但如果時間結束前,notify被調用,則提前醒來。

    Object.notifyAll():喚醒監視器上所有的線程。

    9.2 生產者和消費者案例

  • 包子
  • public class BaoZi {String pi;//包子皮String xian;//包子餡boolean flag = false;//是否有包子 }
  • 包子鋪
  • public class BaoZiPu extends Thread {private BaoZi bz;//鎖對象public BaoZiPu(BaoZi bz){this.bz = bz;}@Overridepublic void run() {int count = 0;//持續生產包子while(true){synchronized (bz){if(bz.flag == true){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;bz.notify();System.out.println(bz.pi+bz.xian+"包已生產好,吃貨可以開始吃包子了");}}} }
  • 吃貨
  • public class ChiHuo extends Thread {private BaoZi bz;public ChiHuo(BaoZi bz){this.bz = bz;}@Overridepublic void run() {while(true){synchronized (bz){if(bz.flag == false){try {bz.wait();} catch (InterruptedException e) {e.printStackTrace();}}System.out.println("吃貨正在吃"+bz.pi+bz.xian+"包");//吃完包子bz.flag = false;//喚醒包子鋪bz.notify();System.out.println("吃貨已經吃完了"+bz.pi+bz.xian+"包,包子鋪開始生產包子");System.out.println("===================================================");}}} }
  • 測試類
  • public class Test {public static void main(String[] args) {BaoZi bz = new BaoZi();BaoZiPu bzp = new BaoZiPu(bz);ChiHuo ch = new ChiHuo(bz);bzp.start();//生產包子ch.start();//吃包子} }

    10 線程池

    10.1 概念

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

    線程池的優點

  • 降低資源消耗。減少了創建和銷毀線程的次數,每個工作線程都可以被重復利用,可執行多個任務。
  • 提高響應速度。當任務到達時,任務可以不需要的等到線程創建就能立即執行。
  • 提高線程的可管理性。可以根據系統的承受能力,調整線程池中工作線線程的數目,防止因為消耗過多的內存,而把服務器累趴下(每個線程需要大約1MB內存,線程開的越多,消耗的內存也就越大,最后死機)。
  • 10.2 線程池的使用

    JDK1.5出現線程池的工廠類Executor用來生產線程池

    Executors類的靜態方法:

    • newFixedThreadPool(int nThread):創建可重用固定線程數的線程池,返回值是ExecutorService接口的實現類對象,使用ExecutorService接口接收【面向接口編程】

    ExecutorService接口:

    • shutdown:關閉銷毀線程池
    • submit(Runnable task):提交一個Runnable任務用于執行

    使用步驟

  • 使用工廠類Executors里面的靜態方法newFixedThreadPool生產一個線程池
  • 創建一個實現類,實現Runnable,重寫run,設置線程任務
  • 調用ExecutorService中的方法submit,傳遞線程任務(實現類),開啟線程,執行run方法。
  • 調用ExecutorService中的方法shutdown銷毀線程池【不建議銷毀線程池】
  • public class RunnableImpl implements Runnable {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"創建了一個新的線程執行");} } public class ThreadPool {public static void main(String[] args) {ExecutorService es = Executors.newFixedThreadPool(2);es.submit(new RunnableImpl());es.submit(new RunnableImpl());es.submit(new RunnableImpl());es.submit(new RunnableImpl());} }

    pool-1-thread-2創建了一個新的線程執行
    pool-1-thread-1創建了一個新的線程執行
    pool-1-thread-2創建了一個新的線程執行
    pool-1-thread-1創建了一個新的線程執行

    總結

    以上是生活随笔為你收集整理的【Java】深入理解Java线程的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。