Java 前台線程后台線程 關於線程


 

 

一.  java中的后台線程:

      java線程大家都不陌生,可以用繼承Thread類或者實現Runable接口來定義一個自己的線程。另外,java線程還可以分為前台線程(也稱用戶線程或普通線程)和后台線程(Daemon thread),本篇就是要說明這兩者的區別和用法。
     1.  后台線程會隨着主程序的結束而結束,但是前台進程則不會;或者說,只要有一個前台線程未退出,進程就不會終止。(下面的例子會充分說明這一點);      2.  默認情況下,程序員創建的線程是用戶線程;用setDaemon(true)可以設置線程為后台線程;而用isDaemon( )則可以判斷一個線程是前台線程還是后台線程;      3. jvm的垃圾回收器其實就是一個后台線程;      4. setDaemon函數必須在start函數之前設定,否則會拋出IllegalThreadStateException異常;

 

轉自大神: https://blog.csdn.net/qq_33945246/article/details/89964186

一、Java 分為兩種線程:用戶線程和守護線程
所謂守護線程是指在程序運行的時候在后台提供一種通用服務的線程,比如垃圾回收線程就是一個很稱職的守護者,並且這種線程並不屬於程序中不可或缺的部分。因 此,當所有的非守護線程結束時,程序也就終止了,同時會殺死進程中的所有守護線程。反過來說,只要任何非守護線程還在運行,程序就不會終止。

守護線程和用戶線程的沒啥本質的區別:唯一的不同之處就在於虛擬機的離開:如果用戶線程已經全部退出運行了,只剩下守護線程存在了,虛擬機也就退出了。 因為沒有了被守護者,守護線程也就沒有工作可做了,也就沒有繼續運行程序的必要了。

將線程轉換為守護線程可以通過調用 Thread 對象的 setDaemon (true) 方法來實現。在使用守護線程時需要注意一下幾點:

(1) thread.setDaemon (true) 必須在 thread.start () 之前設置,否則會跑出一個 IllegalThreadStateException 異常。你不能把正在運行的常規線程設置為守護線程。

(2) 在 Daemon 線程中產生的新線程也是 Daemon 的。

(3) 守護線程應該永遠不去訪問固有資源,如文件、數據庫,因為它會在任何時候甚至在一個操作的中間發生中斷。

二、用戶線程就是前台線程,守護線程就是后台線程
main () 函數即主函數,是一個前台線程,前台進程是程序中必須執行完成的,而后台線程則是 java 中所有前台結束后結束,不管有沒有完成,后台線程主要用與內存分配等方面。
前台線程和后台線程的區別和聯系:

1、后台線程不會阻止進程的終止。屬於某個進程的所有前台線程都終止后,該進程就會被終止。所有剩余的后台線程都會停止且不會完成。
2、不管是前台線程還是后台線程,如果線程內出現了異常,都會導致進程的終止。

3、托管線程池中的線程都是后台線程,使用 new Thread 方式創建的線程默認都是前台線程。

說明:
應用程序的主線程以及使用 Thread 構造的線程都默認為前台線程
使用 Thread 建立的線程默認情況下是前台線程,在進程中,只要有一個前台線程未退出,進程就不會終止。主線程就是一個前台線程。而后台線程不管線程是否結束,只要所有的前台線程都退出(包括正常退出和異常退出)后,進程就會自動終止。一般后台線程用於處理時間較短的任務,如在一個 Web 服務器中可以利用后台線程來處理客戶端發過來的請求信息。而前台線程一般用於處理需要長時間等待的任務,如在 Web 服務器中的監聽客戶端請求的程序,或是定時對某些系統資源進行掃描的程序

-------------------------------------------------------------------------------------------------------------

main()線程是前台線程還是后台線程?前后台線程的區別是什么?

main()線程是程序的主線程,后台線程在主線程完了后自動會退出的。

 

 

 

線程同步:

看到個大神:https://blog.csdn.net/ghsau/article/details/7424694 

要說明線程同步問題首先要說明Java線程的兩個特性,可見性和有序性。多個線程之間是不能直接傳遞數據交互的,它們之間的交互只能通過共享變量來實現

拿上篇博文中的例子來說明,在多個線程之間共享了Count類的一個對象,這個對象是被創建在主內存(堆內存)中,每個線程都有自己的工作內存(線程棧),工作內存存儲了主內存Count對象的一個副本,當線程操作Count對象時,首先從主內存復制Count對象到工作內存中,然后執行代碼count.increment(),改變了num值,最后用工作內存Count刷新主內存Count當一個對象在多個內存中都存在副本時,如果一個內存修改了共享變量,其它線程也應該能夠看到被修改后的值,此為可見性。 多個線程執行時,CPU對線程的調度是隨機的,我們不知道當前程序被執行到哪步就切換到了下一個線程,一個最經典的例子就是銀行匯款問題,一個銀行賬戶存款100,這時一個人從該賬戶取10元,同時另一個人向該賬戶匯10元,那么余額應該還是100。那么此時可能發生這種情況,A線程負責取款,B線程負責匯款,A從主內存讀到100,B從主內存讀到100,A執行減10操作,並將數據刷新到主內存,這時主內存數據100-10=90,而B內存執行加10操作,並將數據刷新到主內存,最后主內存數據100+10=110,顯然這是一個嚴重的問題,我們要保證A線程和B線程有序執行,先取款后匯款或者先匯款后取款,此為有序性。本文講述了JDK5.0之前傳統線程的同步方式,更高級的同步方式可參見Java線程(八):鎖對象Lock-同步問題更完美的處理方式。

        下面同樣用代碼來展示一下線程同步問題。

        TraditionalThreadSynchronized.java:創建兩個線程,執行同一個對象的輸出方法。

public class TraditionalThreadSynchronized {
    public static void main(String[] args) {
        final Outputter output = new Outputter();
        new Thread() {
            public void run() {
                output.output("zhangsan");
            }
        }.start();        
        new Thread() {
            public void run() {
                output.output("lisi");
            }
        }.start();
    }
}
class Outputter {
    public void output(String name) {
        // TODO 為了保證對name的輸出不是一個原子操作,這里逐個輸出name的每個字符
        for(int i = 0; i < name.length(); i++) {
            System.out.print(name.charAt(i));
             Thread.sleep(10);
        }
    }
}

  運行結果:zhlainsigsan

        顯然輸出的字符串被打亂了,我們期望的輸出結果是zhangsanlisi,這就是線程同步問題,我們希望output方法被一個線程完整的執行完之后再切換到下一個線程,Java中使用synchronized保證一段代碼在多線程執行時是互斥的,有兩種用法:

        1. 使用synchronized將需要互斥的代碼包含起來,並上一把鎖。

{
    synchronized (this) {
        for(int i = 0; i < name.length(); i++) {
            System.out.print(name.charAt(i));
        }
    }
}

這把鎖必須是需要互斥的多個線程間的共享對象,像下面的代碼是沒有意義的。

{
    Object lock = new Object();
    synchronized (lock) {
        for(int i = 0; i < name.length(); i++) {
            System.out.print(name.charAt(i));
        }
    }
}

每次進入output方法都會創建一個新的lock,這個鎖顯然每個線程都會創建,沒有意義。

        2. 將synchronized加在需要互斥的方法上。

public synchronized void output(String name) {
    // TODO 線程輸出方法
    for(int i = 0; i < name.length(); i++) {
        System.out.print(name.charAt(i));
    }
}

這種方式就相當於用this鎖住整個方法內的代碼塊,如果用synchronized加在靜態方法上,就相當於用××××.class鎖住整個方法內的代碼塊。使用synchronized在某些情況下會造成死鎖,死鎖問題以后會說明。使用synchronized修飾的方法或者代碼塊可以看成是一個原子操作。

        每個鎖對象(JLS中叫monitor)都有兩個隊列,一個是就緒隊列,一個是阻塞隊列,就緒隊列存儲了將要獲得鎖的線程,阻塞隊列存儲了被阻塞的線程,當一個線程被喚醒(notify)后,才會進入到就緒隊列,等待CPU的調度,反之,當一個線程被wait后,就會進入阻塞隊列,等待下一次被喚醒,這個涉及到線程間的通信,下一篇博文會說明。看我們的例子,當第一個線程執行輸出方法時,獲得同步鎖,執行輸出方法,恰好此時第二個線程也要執行輸出方法,但發現同步鎖沒有被釋放,第二個線程就會進入就緒隊列,等待鎖被釋放。一個線程執行互斥代碼過程如下:

        1. 獲得同步鎖;

        2. 清空工作內存;

        3. 從主內存拷貝對象副本到工作內存;

        4. 執行代碼(計算或者輸出等);

        5. 刷新主內存數據;

        6. 釋放同步鎖。

        所以,synchronized既保證了多線程的並發有序性,又保證了多線程的內存可見性。

        volatile是第二種Java多線程同步的機制,根據JLS(Java LanguageSpecifications)的說法,一個變量可以被volatile修飾,在這種情況下內存模型(主內存和線程工作內存)確保所有線程可以看到一致的變量值,來看一段代碼:

class Test {
    static int i = 0, j = 0;
    static void one() {
        i++;
        j++;
    }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

  一些線程執行one方法,另一些線程執行two方法,two方法有可能打印出j比i大的值,按照之前分析的線程執行過程分析一下:

        1. 將變量i從主內存拷貝到工作內存;

        2. 改變i的值;

        3. 刷新主內存數據;

        4. 將變量j從主內存拷貝到工作內存;

        5. 改變j的值;

        6. 刷新主內存數據;

        這個時候執行two方法的線程先讀取了主存i原來的值又讀取了j改變后的值,這就導致了程序的輸出不是我們預期的結果,要阻止這種不合理的行為的一種方式是在one方法和two方法前面加上synchronized修飾符:

class Test {
    static int i = 0, j = 0;
    static synchronized void one() {
        i++;
        j++;
    }
    static synchronized void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

根據前面的分析,我們可以知道,這時one方法和two方法再也不會並發的執行了,i和j的值在主內存中會一直保持一致,並且two方法輸出的也是一致的。另一種同步的機制是在共享變量之前加上volatile:

class Test {
    static volatile int i = 0, j = 0;
    static void one() {
        i++;
        j++;
    }
    static void two() {
        System.out.println("i=" + i + " j=" + j);
    }
}

one方法和two方法還會並發的去執行,但是加上volatile可以將共享變量i和j的改變直接響應到主內存中,這樣保證了主內存中i和j的值一致性,然而在執行two方法時,在two方法獲取到i的值和獲取到j的值中間的這段時間,one方法也許被執行了好多次,導致j的值會大於i的值。所以volatile可以保證內存可見性,不能保證並發有序性。

       沒有明白JLS中為什么使用兩個變量來闡述volatile的工作原理,這樣不是很好理解。volatile是一種弱的同步手段,相對於synchronized來說,某些情況下使用,可能效率更高,因為它不是阻塞的,尤其是讀操作時,加與不加貌似沒有影響,處理寫操作的時候,可能消耗的性能更多些。但是volatile和synchronized性能的比較,我也說不太准,多線程本身就是比較玄的東西,依賴於CPU時間分片的調度,JVM更玄,還沒有研究過虛擬機,從頂層往底層看往往是比較難看透的。在JDK5.0之前,如果沒有參透volatile的使用場景,還是不要使用了,盡量用synchronized來處理同步問題,線程阻塞這玩意簡單粗暴。另外volatile和final不能同時修飾一個字段,可以想想為什么。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM