第一章 走進java世界中的線程 《java多線程編程實戰指南-核心篇》

進程是程序向操作系統申請資源(如內存和文件句柄)的基本單位。線程是進程中可獨立執行的最小單位。

一個進程可以包含多個線程,同一個進程中的所有線程共享該進程的資源。

java線程API簡介

在java平臺中創建一個線程就是創建一個Thread類或其子類,run方法是該線程的主要執行邏輯;啓動一個線程是手動調用線程的start方法,該方法的作用實際是請求java虛擬機運行相應的線程,但是這個線程具體何時能夠運行是由線程調度器決定的,這個線程可能會立馬被jvm執行,也可能等一會纔會被執行,甚至不會被執行,這是由jvm來控制。

package JavaCoreThreadPatten.capter01;

import java.util.concurrent.TimeUnit;

public class WelcomeApp {
    public static void main(String[] args){
        Thread thread = new WelcomeThread();
        thread.start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.start();
        System.out.println("父線程:"+Thread.currentThread().getName());
    }
}
class WelcomeThread extends Thread{
    @Override
    public void run() {
        System.out.println("子線程:"+Thread.currentThread().getName());
    }
}
package JavaCoreThreadPatten.capter01;

public class WelcomeApp1 {
    public static void main(String[] args){
        Thread thread = new Thread(new WelcomeThread1());
        thread.start();
        System.out.println("父線程:"+Thread.currentThread().getName());
    }
}

class WelcomeThread1 implements Runnable{
    @Override
    public void run() {
        System.out.println("子線程:"+Thread.currentThread().getName());
    }
}

運行結束的線程所佔用的資源會如同其他java對象一樣被java虛擬機回收。

線程屬於一次性用品,不能重複調用線程的start方法使其重複運行。

在java平臺中,一個線程就是一個對象,對象的創建離不開內存空間的分配。創建一個線程與創建其他類型的java對象所不同的是,java虛擬機會對每個線程分配調用棧所需的內存空間。調用棧用於跟蹤java代碼間的調用關係以及java代碼對本地代碼的調用。java平臺中的每個線程可能還有一個內核線程與之對應,因此創建線程對象比創建其他類型的對象成本要高一些。

package JavaCoreThreadPatten.capter01;

import java.util.Random;

public class ThreadCreationCmp {

    /**
     * 執行結果:
         CounterThread :10
         CounterThread :10
         CounterThread :10
         CounterThread :10
         CounterThread :10
         CounterThread :10
         CounterThread :10
         CounterThread :10
         CounterThread :10
         CounterThread :10
         CounterThread :10
         CounterThread :10
         CounterTask :1060
         CounterTask :1124
         CounterTask :1138
         CounterTask :1149
         CounterTask :1165
         CounterTask :1166
         CounterTask :1174
         CounterTask :1178
         CounterTask :1189
         CounterTask :1189
         CounterTask :1189
         CounterTask :1195
     * @param args
     */
    public static void main(String[] args){
        Thread t;
        CountingTask countingTask = new CountingTask();
        //獲取處理器個數
        final int numberOfProceesors = Runtime.getRuntime().availableProcessors();

        for(int i=0;i<2*numberOfProceesors;i++){
            //直接創建一個線程,共用一個runnable實例,以runnable的形式啓動線程
            t = new Thread(countingTask);
            t.start();
        }

        for(int i=0;i<2*numberOfProceesors;i++){
            //以子類的形式直接啓動線程
            t = new CounterThread();
            t.start();
        }
    }

    static class Counter{

        private int count = 0;

        public void increace(){
            count++;
        }

        public int getCount(){
            return count;
        }
    }

    static class CountingTask implements Runnable{

        Counter counter = new Counter();

        @Override
        public void run() {
            for(int i=0;i<100;i++){
                counter.increace();
                doSomething();
            }
            System.out.println("CounterTask :"+counter.getCount());
        }

        private void doSomething(){
            Random random = new Random();
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    static class CounterThread extends Thread{
        Counter counter = new Counter();
        @Override
        public void run() {
            for(int i=0;i<10;i++){
                doSomething();
                counter.increace();
            }
            System.out.println("CounterThread :"+counter.getCount());
        }
        private void doSomething(){
            Random random = new Random();
            try {
                Thread.sleep(random.nextInt(100));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}


線程的屬性包括線程的編號、名稱、類別、優先級。線程的類別需在線程啓動前進行設置,即對setDaemon方法的調用必須在對start方法的調用之前,true標識爲守護線程,否則爲用戶線程。

java線程的優先級屬性本質上只是一個給線程調度器的提示信息,以便於線程調度器決定優先調度哪些線程運行。但是並不能保證線程按照其優先級高低的順序運行。java線程的優先級使用不當或者濫用則可能導致某些線程無法得到運行,即產生線程飢餓。

java中線程分爲守護線程和用戶線程。用戶線程會阻止java虛擬機的正常停止,即一個java虛擬機只有在其所有用戶線程都運行結束的情況下才能正常停止。而守護線程則不會影響java虛擬機的正常停止,即應用程序中有守護線程在運行時也不影響java虛擬機的正常停止。

線程類的常用方法

run():一般由java虛擬機直接調用,用於實現線程任務處理邏輯。

start():啓動相應線程,只能被調一次,該方法被調用不代表線程已經被啓動。

join():等待相應的線程運行結束,若線程A調用線程B的join方法,那麼線程A的運行會被暫停,直到線程B運行結束。相當於執行該方法的線程告訴線程調度器:先暫停一下,等另一個線程運行結束後再繼續運行。

yield():使當前線程主動放棄其對處理器的佔用,這可能會導致當前線程被暫停。相當於執行該方法的線程告訴線程調度器:不着急,如果別人需要處理器資源先給他用,當沒有其他人用,我也不介意繼續佔用。

package JavaCoreThreadPatten.capter01;

/**
 * 實現簡單的計時器
 */
public class SimpleTimer {
    private static int i = 60;
    public static void main(String[] args){
        while (true){
            if (i==0){
                break;
            }else {
                System.out.println("wait me :"+ i +" second");
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            countDown();
        }
    }
    private static void countDown(){
        i--;
    }
}

線程的層次關係

假設線程A所執行的代碼創建了線程B,那麼B是A的子線程,A是B的父線程。

線程的生命週期

java線程的狀態通過Thread.getState()來獲取,包括以下幾種:

NEW:一個已創建而未啓動的線程處於該狀態。由於一個線程實例只能夠被啓動一次,因此一個線程只可能有一次處於該狀態。

RUNNABLE:包括兩個子狀態:READY和RUNNING。前者標識處於該狀態的線程可以被線程調度器進行調度而使之處於RUNNING狀態。後者表示處於該狀態的線程正在運行。執行Thread.yield()的線程,其狀態可能會由RUNNING轉換爲READY。處於READY子狀態的線程也被稱爲活躍線程。

BLOCKED:一個線程發起一個阻塞式I/O操作後,或者申請一個由其他線程持有的獨佔資源(鎖)時,相應的線程會處於該狀態。處於BLOCKED狀態的線程並不會佔用處理器資源。當阻塞式I/O操作完成後,或者線程獲得了其申請的資源,該線程的狀態又可以轉換爲RUNNING。

WAITING:一個線程執行了某些特定方法之後就會處於這種等待其他線程執行另一些特定操作的狀態。能夠使其執行線程變更未WAITING狀態的方法包括:Object.wait(),Thread.join()和LockSuppot.park(Object)。能夠使相應的線程從WAITING變更爲RUNNABLE的相應方法包括:Object.notify()/notifyAll()和LockSuppot.unpark(Object)。

TIMED_WAITING:該狀態和WAITING類似,差別在於處於該狀態的線程並非無限制的等待其他線程執行特定操作,而是處於帶有時間限制的等待操作。當其他線程沒有在指定時間內執行該線程所期望的特定操作時,該線程的狀態自動轉換爲RUNNABLE。

TERMINATED:已經執行結束的線程處於該狀態。由於一個線程實例只能夠被啓動一次,因此一個線程也只有可能有一次處於該狀態。

線程的監視

對線程進行監視的主要途徑是獲取並查看程序的線程轉儲(Thread Dump)。

package JavaCoreThreadPatten.capter01;

import java.net.MalformedURLException;
import java.net.URL;

/**
 * 使用多線程下載文件
 */
public class FileDownloaderApp {
    public static void main(String[] args){
        Thread thread = null;
        for(String url:args){
            thread = new Thread(new FileDownloader(url));
            thread.start();
        }
    }
    static class FileDownloader implements Runnable{

        private final String url;

        public FileDownloader(String url){
            this.url = url;
        }

        @Override
        public void run() {
            String fileBaseName = url.substring(url.lastIndexOf('/')+1);
            try {
                URL url = new URL(this.url);
                String localFileName = System.getProperty("java.io.tmpdir")+"/viscent-"+fileBaseName;
                download(url,localFileName);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            }
        }
        private void download(URL url,String dir){
            //將文件保存到指定地址中
        }
    }
}

多線程編程的優勢和風險

優勢:1.提高系統吞吐率;2.提高響應性;3.充分利用多核處理器資源;4.最小化對系統資源的使用;5.簡化程序的結構。

風險:1.線程安全問題;2.線程活性問題:死鎖、活鎖(一個線程一直嘗試某個操作但就是無法進展)、線程飢餓(某些線程永遠無法獲取處理器執行的計劃而永遠處於RUNNABLE狀態的READY子狀態);3.上下文切換;4.可靠性

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章