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

入門Java線程基礎(chǔ)一篇就夠了

 更新時(shí)間:2021年06月18日 16:52:58   作者:Liziba  
線程是進(jìn)程中的一個(gè)實(shí)體,是被系統(tǒng)獨(dú)立調(diào)度和分派的基本單位,線程自己不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中必不可少的資源,但它可與同屬一個(gè)進(jìn)程的其它線程共享進(jìn)程所擁有的全部資源

簡介

線程是操作系統(tǒng)調(diào)度的最小單元,在多核環(huán)境中,多個(gè)線程能同時(shí)執(zhí)行,如果運(yùn)用得當(dāng),能顯著的提升程序的性能。

一、線程初步認(rèn)識(shí)

1、什么是線程

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

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

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

[5]Attach Listener // JVM進(jìn)程間的通信線程

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

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

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

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

總結(jié):從輸出結(jié)果不難看出,Java程序本身就是多線程的。它不僅僅只有一個(gè)main線程在運(yùn)行,而是main線程和其他多個(gè)線程在同時(shí)運(yùn)行。

3、為什么要使用多線程

使用多線程的好處如下:

1.更多處理器核心

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

2.更快響應(yīng)時(shí)間

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

3.更好的編程模型

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

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

現(xiàn)代操作系統(tǒng)基本采用的是時(shí)間片分配的方式來調(diào)度線程,也就是操作系統(tǒng)將CPU的運(yùn)行分為一個(gè)個(gè)時(shí)間片,線程會(huì)分配的若干時(shí)間片,當(dāng)線程時(shí)間片用完了,就會(huì)發(fā)生線程調(diào)度等待下次時(shí)間片的分配。線程在一次CPU調(diào)度中能執(zhí)行多久,取決于所分時(shí)間片的多少,而線程優(yōu)先級(jí)就是決定線程需要多或者少分配一些處理器資源的線程屬性。在Java線程中,線程的優(yōu)先級(jí)的可設(shè)置范圍是1-10,默認(rèn)優(yōu)先級(jí)是5,理論上優(yōu)先級(jí)高的線程分配時(shí)間片數(shù)量要優(yōu)先于低的線程(部分操作系統(tǒng)這個(gè)設(shè)置是不生效的); ​

示例代碼

package com.lizba.p2;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
 * <p>
 *      線程優(yōu)先級(jí)設(shè)置
 * </p>
 *
 * @Author: Liziba
 * @Date: 2021/6/14 12:03
 */
public class Priority {
    /** 線程執(zhí)行流程控制開關(guān) */
    private static volatile boolean notStart = true;
    /** 線程執(zhí)行流程控制開關(guān) */
    private static volatile boolean notEnd = true;
    public static void main(String[] args) throws InterruptedException {
        List<Job> jobs = new ArrayList<>();
        // 設(shè)置5個(gè)優(yōu)先級(jí)為1的線程,設(shè)置5個(gè)優(yōu)先級(jí)為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)先級(jí)
     */
    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時(shí)間片,等待下次調(diào)度
                Thread.yield();
            }
            while (notEnd) {
                // 讓出CPU時(shí)間片,等待下次調(diào)度
                Thread.yield();
                jobCount++;
            }
        }
    }
}

執(zhí)行結(jié)果

在這里插入圖片描述

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

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

線程的生命周期如下:

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

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

代碼示例:

package com.lizba.p2;
import java.util.concurrent.TimeUnit;
/**
 * <p>
 *      睡眠指定時(shí)間工工具類
 * </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一個(gè)獲取鎖成功,一個(gè)獲取失敗
        new Thread(new Blocked(), "Blocked1Thread").start();
        new Thread(new Blocked(), "Blocked2Thread").start();
    }
    // 線程不斷的進(jìn)行睡眠
    static class TimeWaiting implements Runnable {
        @Override
        public void run() {
            while (true) {
                SleepUtil.sleepSecond(100);
            }
        }
    }
    // 線程等待在Waiting.class實(shí)例上
    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實(shí)例上加鎖,不會(huì)釋放該鎖
    static class Blocked implements Runnable {
        @Override
        public void run() {
            synchronized (Blocked.class) {
                while (true) {
                    SleepUtil.sleepSecond(100);
                }
            }
        }
    }
}

使用JPS查看Java進(jìn)程

在這里插入圖片描述

查看示例代碼ThreadStateDemo進(jìn)程ID是2576,鍵入jstack 2576查看輸出:

在這里插入圖片描述

整理輸出結(jié)果在這里插入圖片描述

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

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

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

在這里插入圖片描述

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

總結(jié):

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

6、Daemon線程

簡介:

Daemon線程是一種支持型線程,它的主要作用是程序中后臺(tái)調(diào)度和支持性工作。當(dāng)一個(gè)Java虛擬機(jī)中不存在非Daemon線程的時(shí)候,Java虛擬機(jī)將會(huì)退出。Daemon線程需要在啟動(dòng)之前設(shè)置,不能在啟動(dòng)之后設(shè)置。

設(shè)置方式

Thread.setDaemon(true)

需要特別注意的點(diǎn)

Daemon線程被用作支持性工作的完成,但是在Java虛擬機(jī)退出時(shí)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();
    }
}

輸出結(jié)果

在這里插入圖片描述

總結(jié)

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

二、線程啟動(dòng)和終止

1、構(gòu)造線程

運(yùn)行線程之前需要構(gòu)造一個(gè)線程對象,線程對象在構(gòu)造的時(shí)候需要設(shè)置一些線程的屬性,這些屬性包括線程組、線程的優(yōu)先級(jí)、是否是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");        }// 設(shè)置線程名稱        this.name = name;// 當(dāng)前線程設(shè)置為該線程的父線程        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();// 設(shè)置線程組        this.group = g;        // 將daemon屬性設(shè)置為父線程的對應(yīng)的屬性        this.daemon = parent.isDaemon();        // 將prority屬性設(shè)置為父線程的對應(yīng)的屬性        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);        // 復(fù)制父線程的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;       // 設(shè)置一個(gè)線程id        tid = nextThreadID();    }

總結(jié)

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

2、什么是線程中斷

中斷可以理解為線程的一個(gè)標(biāo)識(shí)位屬性,它表示一個(gè)運(yùn)行中的線程是否被其他線程進(jìn)行了中斷操作。線程通過檢查自身是否被中斷來進(jìn)行響應(yīng),線程通過方法isInterrupted()來進(jìn)行判斷是否被中斷,也可以通過調(diào)用靜態(tài)方法Thread.interrupted()對當(dāng)前線程的中斷標(biāo)志位進(jìn)行復(fù)位。如下情況不能準(zhǔn)確判斷線程是否被中斷過:

線程已經(jīng)終止運(yùn)行,即使被中斷過,isInterrupted()方法也會(huì)返回false方法拋出InterruptedException異常,即使被中斷過,調(diào)用isInterrupted()方法將會(huì)返回false,這是因?yàn)閽伋鯥nterruptedException之前會(huì)清除中斷標(biāo)志。

示例代碼:

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);
        // 啟動(dòng)兩個(gè)線程
        sleepThread.start();
        busyThread.start();
        // 休眠5秒,讓sleepThread和busyThread運(yùn)行充分
        SleepUtil.sleepSecond(5);
        // 中斷兩個(gè)線程
        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) {}
        }
    }
}

查看運(yùn)行結(jié)果:

在這里插入圖片描述

總結(jié)

拋出InterruptedException的是sleepThread線程,雖然兩者都被中斷過,但是sleepThread線程的中斷標(biāo)志返回的是false,這是因?yàn)門imeUnit.SECONDS.sleep(seconds)會(huì)拋出InterruptedException異常,拋出異常之前,sleepThread線程的中斷標(biāo)志被清除了。但是,busyThread一直在運(yùn)行沒有拋出異常,中斷位沒有被清除。 ​

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

舉例:

線程這三個(gè)方法,相當(dāng)于QQ音樂播放音樂時(shí)的暫停、恢復(fù)和停止操作。(注意這些方法已經(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);
        // 恢復(fù)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);
           }
        }
    }
}

輸出結(jié)果

在這里插入圖片描述

總結(jié)

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

存在問題

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

4、正確的終止線程

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

示例代碼

package com.lizba.p2;
/**
 * <p>
 *      標(biāo)志位終止線程示例代碼
 * </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);
        }
        /**
         * 關(guān)閉
         */
        public void cancel() {
            on = false;
        }
    }
}

輸出結(jié)果:

在這里插入圖片描述

總結(jié):

main線程通過中斷操作和cancel()方法均可使CountThread得以終止。這兩種方法終止線程的好處是能讓線程在終止時(shí)有機(jī)會(huì)去清理資源。做法更加安全和優(yōu)雅。希望大家可以多多關(guān)注腳本之家的更多內(nèi)容! ​

相關(guān)文章

最新評(píng)論