欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

java線程的基礎實例解析

 更新時間:2021年06月24日 16:17:29   作者:Liziba  
java中線程的基本方法的熟練使用是精通多線程編程的必經(jīng)之路,線程相關的基本方法有wait,notify,notifyAll,sleep,join,yield等,本文淺要的介紹一下它們的使用方式

一、線程初步認識

1、什么是線程

操作系統(tǒng)運行一個程序會為其啟動一個進程。例如,啟動一個Java程序會創(chuàng)建一個Java進程。現(xiàn)代操作系統(tǒng)調(diào)度的最小單元是線程,線程也稱為輕量級進程(Light Weight Process),一個進程中可以創(chuàng)建一個到多個線程,線程擁有自己的計數(shù)器、堆棧和局部變量等屬性,并且能訪問共享的內(nèi)存變量。處理器會通過快速切換這些線程,來執(zhí)行程序。

2、Java本身就是多線程

示例代碼

package com.lizba.p2;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.Arrays;
/**
 *
 * @Author: Liziba
 * @Date: 2021/6/13 23:03
 */
public class MultiThread {
    public static void main(String[] args) {
        // 獲取Java線程管理MXBean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 獲取線程和線程堆棧信息;
        // boolean lockedMonitors = false  不需要獲取同步的monitor信息;
        // boolean lockedSynchronizers = false  不需要獲取同步的synchronizer信息
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        // 打印線程ID和線程name
        Arrays.stream(threadInfos).forEach(threadInfo -> {
            System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName());
        });
    }
}

輸出結果(不一定一致):

[6]Monitor Ctrl-Break // idea中特有的線程(不用管)

[5]Attach Listener // JVM進程間的通信線程

[4]Signal Dispatcher // 分發(fā)處理發(fā)送給JVM信號的線程

[3]Finalizer // 調(diào)用對象的finalizer線程

[2]Reference Handler // 清楚Reference的線程

[1]main // main線程,用戶程序入口

總結:從輸出結果不難看出,Java程序本身就是多線程的。它不僅僅只有一個main線程在運行,而是main線程和其他多個線程在同時運行。

3、為什么要使用多線程

使用多線程的好處如下:

1.更多處理器核心

計算機處理器核心數(shù)增多,由以前的高主頻向多核心技術發(fā)展,現(xiàn)在的計算機更擅長于并行計算,因此如何充分利用多核心處理器是現(xiàn)在的主要問題。線程是操作系統(tǒng)調(diào)度的最小單元,一個程序作為一個進程來運行,它會創(chuàng)建多個線程,而一個線程在同一時刻只能運行在一個處理器上。因此一個進程如果能使用多線程計算,將其計算邏輯分配到多個處理器核心上,那么相比單線程運行將會有更顯著的性能提升。

2.更快響應時間

在復雜業(yè)務場景中,我們可以將非強一致性關聯(lián)的業(yè)務派發(fā)給其他線程處理(或者使用消息隊列)。這樣可以減少應用響應用戶請求的時間

3.更好的編程模型

合理使用Java的提供的多線程編程模型,能使得程序員更好的解決問題,而不需要過于復雜的考慮如何將其多線程化。 ​

4、線程的優(yōu)先級

現(xiàn)代操作系統(tǒng)基本采用的是時間片分配的方式來調(diào)度線程,也就是操作系統(tǒng)將CPU的運行分為一個個時間片,線程會分配的若干時間片,當線程時間片用完了,就會發(fā)生線程調(diào)度等待下次時間片的分配。線程在一次CPU調(diào)度中能執(zhí)行多久,取決于所分時間片的多少,而線程優(yōu)先級就是決定線程需要多或者少分配一些處理器資源的線程屬性。

在Java線程中,線程的優(yōu)先級的可設置范圍是1-10,默認優(yōu)先級是5,理論上優(yōu)先級高的線程分配時間片數(shù)量要優(yōu)先于低的線程(部分操作系統(tǒng)這個設置是不生效的);

示例代碼

package com.lizba.p2;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
 * <p>
 *      線程優(yōu)先級設置
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/14 12:03
 */
public class Priority {
    /** 線程執(zhí)行流程控制開關 */
    private static volatile boolean notStart = true;
    /** 線程執(zhí)行流程控制開關 */
    private static volatile boolean notEnd = true;
    public static void main(String[] args) throws InterruptedException {
        List<Job> jobs = new ArrayList<>();
        // 設置5個優(yōu)先級為1的線程,設置5個優(yōu)先級為10的線程
        for (int i = 0; i < 10; i++) {
            int priority = i < 5 ? Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
            Job job = new Job(priority);
            jobs.add(job);
            Thread thread = new Thread(job, "Thread:" + i);
            thread.setPriority(priority);
            thread.start();
        }
        notStart = false;
        TimeUnit.SECONDS.sleep(10);
        notEnd = false;
        jobs.forEach(
                job -> System.out.println("Job priority : " + job.priority + ", Count : " + job.jobCount)
        );
    }
    /**
     * 通過Job來記錄線程的執(zhí)行次數(shù)和優(yōu)先級
     */
    static class Job implements Runnable {
        private int priority;
        private long jobCount;
        public Job(int priority) {
            this.priority = priority;
        }
        @Override
        public void run() {
            while (notStart) {
                // 讓出CPU時間片,等待下次調(diào)度
                Thread.yield();
            }
            while (notEnd) {
                // 讓出CPU時間片,等待下次調(diào)度
                Thread.yield();
                jobCount++;
            }
        }
    }
}

執(zhí)行結果:

在這里插入圖片描述

從輸出結果上來看,優(yōu)先級為1的線程和優(yōu)先級為10的線程執(zhí)行的次數(shù)非常相近,因此這表明程序正確性是不能依賴線程的優(yōu)先級高低的。

5、線程的狀態(tài)

線程的生命周期如下:

狀態(tài)名稱 說明
NEW 初始狀態(tài),線程被構建,并未調(diào)用start()方法
RUNNABLE 運行狀態(tài),Java線程將操作系統(tǒng)中的就緒和運行兩種狀態(tài)統(tǒng)稱為“運行中”
BLOCKED 阻塞狀態(tài),線程阻塞于鎖
WAITING 等待狀態(tài),線程進入等待狀態(tài),進入該狀態(tài)表示當前線程需要等待其他線程作出一些特定動作(通知或中斷)
TIME_WAITING 超時等待,先比WAITING可以在指定的時間內(nèi)自行返回
TERMINATED 終止狀態(tài),表示當前線程已經(jīng)執(zhí)行完畢

通過代碼來查看Java線程的狀態(tài)

代碼示例:

package com.lizba.p2;
import java.util.concurrent.TimeUnit;
/**
 * <p>
 *      睡眠指定時間工工具類
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/14 13:27
 */
public class SleepUtil {
    public static final void sleepSecond(long seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
package com.lizba.p2;
/**
 * <p>
 *      線程狀態(tài)示例代碼
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/14 13:25
 */
public class ThreadStateDemo {
    public static void main(String[] args) {
        // TimeWaiting
        new Thread(new TimeWaiting(), "TimeWaitingThread").start();
        // Waiting
        new Thread(new Waiting(), "WaitingThread").start();
        // Blocked1和Blocked2一個獲取鎖成功,一個獲取失敗
        new Thread(new Blocked(), "Blocked1Thread").start();
        new Thread(new Blocked(), "Blocked2Thread").start();
    }
    // 線程不斷的進行睡眠
    static class TimeWaiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                SleepUtil.sleepSecond(100);
            }
        }
    }
    // 線程等待在Waiting.class實例上
    static class Waiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                synchronized (Waiting.class) {
                    try {
                        Waiting.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    // 該線程Blocked.class實例上加鎖,不會釋放該鎖
    static class Blocked implements Runnable {
        @Override
        public void run() {
            synchronized (Blocked.class) {
                while (true) {
                    SleepUtil.sleepSecond(100);
                }
            }
        }
    }
}

使用JPS查看Java進程:

在這里插入圖片描述

查看示例代碼ThreadStateDemo進程ID是2576,鍵入jstack 2576查看輸出:

在這里插入圖片描述

整理輸出結果: 在這里插入圖片描述

線程名稱 線程狀態(tài)
Blocked2Thread BLOCKED (on object monitor),阻塞在獲取Blocked.class的鎖上
Blocked1Thread TIMED_WAITING (sleeping)
WaitingThread WAITING (on object monitor)
TimeWaitingThread TIMED_WAITING (sleeping)

總結:

線程在自身生命周期中不是規(guī)定處于某一個狀態(tài),而是隨著代碼的執(zhí)行在不同的狀態(tài)之間進行切換。

Java線程的狀態(tài)變化圖如下:

在這里插入圖片描述

Java線程狀態(tài)變遷圖

總結:

  • 線程創(chuàng)建后,調(diào)用start()方法開始運行
  • 線程執(zhí)行wait()方法后,線程進入等待狀態(tài),進入等待的線程需要依靠其他線程才能夠返回到運行狀態(tài)
  • 超時等待相當于在等待狀態(tài)的基礎上增加了超時限制,達到設置的超時時間后返回到運行狀態(tài)
  • 線程執(zhí)行同步方法或代碼塊時,未獲取到鎖的線程,將會進入到阻塞狀態(tài)。
  • 線程執(zhí)行完Runnable的run()方法之后進入到終止狀態(tài)
  • 阻塞在Java的concurrent包中Lock接口的線程是等待狀態(tài),因為Lock接口阻塞的實現(xiàn)使用的是Daemon線程

​6、Daemon線程

簡介:

Daemon線程是一種支持型線程,它的主要作用是程序中后臺調(diào)度和支持性工作。當一個Java虛擬機中不存在非Daemon線程的時候,Java虛擬機將會退出。Daemon線程需要在啟動之前設置,不能在啟動之后設置。

設置方式:

Thread.setDaemon(true)

需要特別注意的點:

Daemon線程被用作支持性工作的完成,但是在Java虛擬機退出時Daemon線程的finally代碼塊不一定執(zhí)行。

示例代碼:

package com.lizba.p2;
/**
 * <p>
 *      DaemonRunner線程
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/14 19:50
 */
public class DaemonRunner implements Runnable{
    @Override
    public void run() {
        try {
            SleepUtil.sleepSecond(100);
        } finally {
            System.out.println("DaemonRunner finally run ...");
        }
    }
}

測試:

package com.lizba.p2;
/**
 * <p>
 *
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/14 19:59
 */
public class DaemonTest {
    public static void main(String[] args) {
        Thread t = new Thread(new DaemonRunner(), "DaemonRunner");
        t.setDaemon(true);
        t.start();
    }
}

輸出結果:

在這里插入圖片描述

總結:

不難發(fā)現(xiàn),DaemonRunner的run方法的finally代碼塊并沒有執(zhí)行,這是因為,當Java虛擬機中已經(jīng)沒有非Daemon線程時,虛擬機會立即退出,虛擬機中的所以daemon線程需要立即終止,所以線程DaemonRunner會被立即終止,finally并未執(zhí)行。​

二、線程啟動和終止

1、構造線程

運行線程之前需要構造一個線程對象,線程對象在構造的時候需要設置一些線程的屬性,這些屬性包括線程組、線程的優(yōu)先級、是否是daemon線程、線程名稱等信息。

代碼示例:

來自java.lang.Thread

  /**
     * Initializes a Thread.
     *
     * @param g the Thread group
     * @param target the object whose run() method gets called
     * @param name the name of the new Thread
     * @param stackSize the desired stack size for the new thread, or
     *        zero to indicate that this parameter is to be ignored.
     * @param acc the AccessControlContext to inherit, or
     *            AccessController.getContext() if null
     * @param inheritThreadLocals if {@code true}, inherit initial values for
     *            inheritable thread-locals from the constructing thread
     */
    private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
		// 設置線程名稱
        this.name = name;
		// 當前線程設置為該線程的父線程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            if (security != null) {
                g = security.getThreadGroup();
            }
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        g.checkAccess();
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        g.addUnstarted();
		// 設置線程組
        this.group = g;
        // 將daemon屬性設置為父線程的對應的屬性
        this.daemon = parent.isDaemon();
        // 將prority屬性設置為父線程的對應的屬性
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        // 復制父線程的InheritableThreadLocals屬性
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;
       // 設置一個線程id
        tid = nextThreadID();
    }

總結:

在上述代碼中,一個新構建的線程對象時由其parent線程來分配空間的,而child繼承了parent是否為Daemon、優(yōu)先級和加載資源的contextClassLoader以及可繼承的ThreadLocal,同時會分配一個唯一的ID來標志線程。此時一個完整的能夠運行的線程對象就初始化好了,在堆內(nèi)存中等待運行。​

2、什么是線程中斷

中斷可以理解為線程的一個標識位屬性,它表示一個運行中的線程是否被其他線程進行了中斷操作。線程通過檢查自身是否被中斷來進行響應,線程通過方法isInterrupted()來進行判斷是否被中斷,也可以通過調(diào)用靜態(tài)方法Thread.interrupted()對當前線程的中斷標志位進行復位。

如下情況不能準確判斷線程是否被中斷過:

1.線程已經(jīng)終止運行,即使被中斷過,isInterrupted()方法也會返回false

2.方法拋出InterruptedException異常,即使被中斷過,調(diào)用isInterrupted()方法將會返回false,這是因為拋出InterruptedException之前會清除中斷標志。

示例代碼:

package com.lizba.p2;
/**
 * <p>
 *      線程中斷示例代碼
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/14 20:36
 */
public class Interrupted {
    public static void main(String[] args) {
        // sleepThread不停的嘗試睡眠
        Thread sleepThread = new Thread(new SleepRunner(), "sleepThread");
        sleepThread.setDaemon(true);
        // busyThread
        Thread busyThread = new Thread(new BusyRunner(), "busyThread");
        busyThread.setDaemon(true);
        // 啟動兩個線程
        sleepThread.start();
        busyThread.start();
        // 休眠5秒,讓sleepThread和busyThread運行充分
        SleepUtil.sleepSecond(5);
        // 中斷兩個線程
        sleepThread.interrupt();
        busyThread.interrupt();
        System.out.println("SleepThread interrupted is " + sleepThread.isInterrupted());
        System.out.println("BusyThread interrupted is " + busyThread.isInterrupted());
        // 睡眠主線程,防止daemon線程退出
        SleepUtil.sleepSecond(2);
    }
    static class SleepRunner implements Runnable {
        @Override
        public void run() {
            while (true) {
                SleepUtil.sleepSecond(10);
            }
        }
    }
    static class BusyRunner implements Runnable {
        @Override
        public void run() {
            while (true) {}
        }
    }
}

查看運行結果:

在這里插入圖片描述

總結:

拋出InterruptedException的是sleepThread線程,雖然兩者都被中斷過,但是sleepThread線程的中斷標志返回的是false,這是因為TimeUnit.SECONDS.sleep(seconds)會拋出InterruptedException異常,拋出異常之前,sleepThread線程的中斷標志被清除了。但是,busyThread一直在運行沒有拋出異常,中斷位沒有被清除。

3、suspend()、resume()和stop()

舉例:線程這三個方法,相當于QQ音樂播放音樂時的暫停、恢復和停止操作。(注意這些方法已經(jīng)過期了,不建議使用。) ​

示例代碼:

package com.lizba.p2;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
 * <p>
 *      線程過期方法示例
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/14 20:57
 */
public class Deprecated {
    static DateFormat format = new SimpleDateFormat("HH:mm:ss");
    public static void main(String[] args) {
        Thread printThread = new Thread(new PrintThread(), "PrintThread");
        printThread.start();
        SleepUtil.sleepSecond(3);
        // 暫停printThread輸出
        printThread.suspend();
        System.out.println("main suspend PrintThread at " + format.format(new Date()));
        SleepUtil.sleepSecond(3);
        // 恢復printThread輸出
        printThread.resume();
        System.out.println("main resume PrintThread at " + format.format(new Date()));
        SleepUtil.sleepSecond(3);
        // 終止printThread輸出
        printThread.stop();
        System.out.println("main stop PrintThread at " + format.format(new Date()));
        SleepUtil.sleepSecond(3);
    }
    static class PrintThread implements Runnable {
        @Override
        public void run() {
           while (true) {
               System.out.println(Thread.currentThread().getName() + "Run at "
               + format.format(new Date()));
               SleepUtil.sleepSecond(1);
           }
        }
    }
}

輸出結果:

在這里插入圖片描述

總結:

上述代碼執(zhí)行輸出的結果,與API說明和我們的預期完成一致,但是看似正確的代碼卻隱藏這很多問題。​

存在問題:

  • suspend()方法調(diào)用后不會釋放已占有的資源(比如鎖),可能會導致死鎖
  • stop()方法在終結一個線程時不能保證資源的正常釋放,可能會導致程序處于不確定的工作狀態(tài)​

4、正確的終止線程

  1. 調(diào)用線程的interrupt()方法
  2. 使用一個Boolean類型的變量來控制是否停止任務并終止線程

示例代碼:

package com.lizba.p2;
/**
 * <p>
 *      標志位終止線程示例代碼
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/14 21:17
 */
public class ShutDown {
    public static void main(String[] args) {
        Runner one = new Runner();
        Thread t = new Thread(one, "CountThread");
        t.start();
        SleepUtil.sleepSecond(1);
        t.interrupt();
        Runner two = new Runner();
        t = new Thread(two, "CountThread");
        t.start();
        SleepUtil.sleepSecond(1);
        two.cancel();
    }
    private static class Runner implements Runnable {
        private long i;
        private volatile boolean on = true;
        @Override
        public void run() {
            while (on && !Thread.currentThread().isInterrupted()) {
                i++;
            }
            System.out.println("Count i = " +i);
        }
        /**
         * 關閉
         */
        public void cancel() {
            on = false;
        }
    }
}

輸出結果:

在這里插入圖片描述

總結:

main線程通過中斷操作和cancel()方法均可使CountThread得以終止。這兩種方法終止線程的好處是能讓線程在終止時有機會去清理資源。做法更加安全和優(yōu)雅。

相關文章

  • java實現(xiàn)ftp上傳 如何創(chuàng)建文件夾

    java實現(xiàn)ftp上傳 如何創(chuàng)建文件夾

    這篇文章主要為大家詳細介紹了java實現(xiàn)ftp上傳的相關資料,教大家如何創(chuàng)建文件夾?具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-04-04
  • 詳解JVM 運行時內(nèi)存使用情況監(jiān)控

    詳解JVM 運行時內(nèi)存使用情況監(jiān)控

    這篇文章主要介紹了詳解JVM 運行時內(nèi)存使用情況監(jiān)控,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • Java實現(xiàn)求解一元n次多項式的方法示例

    Java實現(xiàn)求解一元n次多項式的方法示例

    這篇文章主要介紹了Java實現(xiàn)求解一元n次多項式的方法,涉及java高斯消元法處理矩陣運算解多項式的相關操作技巧,需要的朋友可以參考下
    2018-01-01
  • eclipse輸出Hello World的實現(xiàn)方法

    eclipse輸出Hello World的實現(xiàn)方法

    這篇文章主要介紹了eclipse輸出Hello World的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-11-11
  • Java 圖文并茂講解主方法中的String[] args參數(shù)作用

    Java 圖文并茂講解主方法中的String[] args參數(shù)作用

    很多老鐵不清楚JAVA主方法中main()里面的的參數(shù)是什么意思,以及有什么作用,接下來給大家用最通俗易懂的話來講解,還不清楚的朋友來看看吧
    2022-04-04
  • jvm運行原理以及類加載器實例詳解

    jvm運行原理以及類加載器實例詳解

    這篇文章主要給大家介紹了關于jvm運行原理以及類加載器的相關資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-05-05
  • springCloud中的Sidecar多語言支持詳解

    springCloud中的Sidecar多語言支持詳解

    這篇文章主要介紹了springCloud中的Sidecar多語言支持詳解,Sidecar是將一組緊密結合的任務與主應用程序共同放在一臺主機Host中,但會將它們部署在各自的進程或容器中,需要的朋友可以參考下
    2024-01-01
  • Java精品項目瑞吉外賣之員工新增篇

    Java精品項目瑞吉外賣之員工新增篇

    這篇文章主要為大家詳細介紹了java精品項目-瑞吉外賣訂餐系統(tǒng),此項目過大,分為多章獨立講解,本篇內(nèi)容為新增員工功能的實現(xiàn),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-05-05
  • SpringBoot動態(tài)修改yml配置文件的方法詳解

    SpringBoot動態(tài)修改yml配置文件的方法詳解

    這篇文章主要為大家詳細介紹了SpringBoot動態(tài)修改yml配置文件的方法,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • 如何實現(xiàn)java8 list按照元素的某個字段去重

    如何實現(xiàn)java8 list按照元素的某個字段去重

    這篇文章主要介紹了如何實現(xiàn)java8 list按照元素的某個字段去重,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,,需要的朋友可以參考下
    2019-06-06

最新評論