程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 【JAVA並發】基本線程機制,java並發線程

【JAVA並發】基本線程機制,java並發線程

編輯:JAVA綜合教程

【JAVA並發】基本線程機制,java並發線程


基本線程機制

一個程序可以被劃分為多個獨立的任務,每個獨立的任務可以由線程來驅動執行;

一個進程可以包含若干個線程,即擁有若干個並發執行的任務,在程序運行時,CPU時間被劃分成片段分配給所有的線程;

在單處理器的機器上使用多線程可以提高性能的原因在於任務阻塞;

為機器增加處理器可以顯著加快使用多線程程序的運行速度;

使用線程機制使程序更加透明、可擴展,代碼不需要知道它是運行在單處理器還是多處理器上;

創建線程方式

方式一、創建一個任務類實現Runnable接口,並將其具體對象提交給Thread構造器

創建一個發射類LiftOff實現Runnable接口:

package concurrency; public class LiftOff implements Runnable { protected int countDown = 10; // Default private static int taskCount = 0; private final int id = taskCount++; public LiftOff() { } public LiftOff(int countDown) { this.countDown = countDown; } public String status() { return Thread.currentThread() + "#" + id + "(" + (countDown > 0 ? countDown : "Liftoff!") + "), "; } public void run() { while (countDown-- > 0) { System.out.println(status()); Thread.yield(); } } } View Code

以上代碼中調用了Thread.yield()方法,該方法的作用是建議線程調度器切換到其它線程執行任務,注意,只是建議,不保證采納;

創建完任務類之後,可以在Main函數中使用LiftOff對象創建一個Thread對象,並調用其start方法啟動該線程,如下:

package concurrency;

public class BasicThreads {
    public static void main(String[] args) {
        Thread t = new Thread(new LiftOff());
        t.start();
        System.out.println(Thread.currentThread() + "Waiting for LiftOff");
    }
} 

打印結果如下,注意該程序中是同時存在兩個線程(main和Thread-0)在運行的;

另外關於Thread對象的打印形式為[Thread-0,5,main],其中依次代表[線程名,線程優先級、線程組名], 具體可查看Thread類的toString方法;

Thread[main,5,main]Waiting for LiftOff
Thread[Thread-0,5,main]#0(9), 
Thread[Thread-0,5,main]#0(8), 
Thread[Thread-0,5,main]#0(7), 
Thread[Thread-0,5,main]#0(6), 
Thread[Thread-0,5,main]#0(5), 
Thread[Thread-0,5,main]#0(4), 
Thread[Thread-0,5,main]#0(3), 
Thread[Thread-0,5,main]#0(2), 
Thread[Thread-0,5,main]#0(1), 
Thread[Thread-0,5,main]#0(Liftoff!), 

最後,提個醒,有些人在創建完任務類後,直接在main函數中新建一個任務類對象,並調用其run方法,如下代碼,運行正常,也看到了run方法中的運行結果,以為創建了線程,其實這種使用方式是錯誤的,並沒有創建任何新線程,只是在main線程裡調用執行了一個普通對象的方法而已;

package concurrency;

public class MainThread {
    public static void main(String[] args) {
        LiftOff launch = new LiftOff();
        launch.run();
    }
} 

方式二、繼承Thread類,調用其具體對象的start方法

package concurrency; public class SimpleThread extends Thread { private int countDown = 5; private static int threadCount = 0; public SimpleThread() { // Store the thread name: super(Integer.toString(++threadCount)); start(); } public String toString() { return "#" + getName() + "(" + countDown + "), "; } public void run() { while (true) { System.out.println(this); if (--countDown == 0) return; } } public static void main(String[] args) { for (int i = 0; i < 5; i++) new SimpleThread(); } } View Code

對比通過實現Runnable接口的方式,該方式不建議使用,因為java的單繼承機制,通常通過實現接口比繼承會更好點;

另外還可以通過內部內部類將線程代碼隱藏在類中,如下寫法;

class InnerThread1 { private int countDown = 5; private Inner inner; private class Inner extends Thread { Inner(String name) { super(name); start(); } public void run() { try { while (true) { print(this); if (--countDown == 0) return; sleep(10); } } catch (InterruptedException e) { print("interrupted"); } } public String toString() { return getName() + ": " + countDown; } } public InnerThread1(String name) { inner = new Inner(name); } } View Code

方式三、創建一個任務類實現Runnable接口,並將其具體對象提交給Executors【推薦】

java.util.concurrent包中的執行器Executors可以幫助我們管理Thread對象,簡化並發編程,如下,可以使用Executors類中的newCachedThreadPool靜態方法創建一個可緩存的線程池,並用其執行相關任務;

package concurrency;

import java.util.concurrent.*;

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++)
            exec.execute(new LiftOff());
        exec.shutdown();
    }
}

在Executors類中,除了通過newCachedThreadPool創建線程池外,還可以創建通過以下方法創建其它種類的線程池:

newFixedThreadPool:固定大小度的線程池

newSingleThreadExecutor:單線程線程池

newScheduledThreadPool:執行定時和周期性任務

方式四、創建一個任務類實現Callable接口,並將其具體對象提交給Executors【推薦】

實現Callable接口的類同樣是一個任務類,與實現Runnable接口的區別是該方式可以有返回值;

在實現Callable接口的類中,線程執行的方法是call方法(有返回值),而不是run方法;

在main方法中可以通過調用ExecutorService的submit方法,返回一個Future對象,通過該對象可以獲取線程運行的返回值,注意需要等Future完成後才能取得結果,可以通過isDone方法來查詢Future是否已完成,或者直接調用get方法來獲取(會阻塞,直到結果准備就緒)。

package concurrency;

import java.util.concurrent.*;
import java.util.*;

class TaskWithResult implements Callable<String> {
    private int id;

    public TaskWithResult(int id) {
        this.id = id;
    }

    public String call() {
        return "result of TaskWithResult " + id;
    }
}

public class CallableDemo {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        ArrayList<Future<String>> results = new ArrayList<Future<String>>();
        for (int i = 0; i < 10; i++)
            results.add(exec.submit(new TaskWithResult(i)));
        for (Future<String> fs : results)
            try {
                System.out.println(fs.get());
            } catch (InterruptedException e) {
                System.out.println(e);
                return;
            } catch (ExecutionException e) {
                System.out.println(e);
            } finally {
                exec.shutdown();
            }
    }
} 

小結

實際上,個人感覺創建線程就兩種方式,通過實現Runnable接口和實現Callable接口,Thread實際上也是實現了Runnable接口的類;

 守護線程(後台線程)

daemon線程是指在程序運行的時候,在後台提供一種通用服務的線程,這種線程的優先級非常低;

當所有其他線程結束時,會殺死進程中的所有守護線程;

可以在線程啟動之前通過setDaemon(true)方法將線程設置為守護線程,注意只能在啟動之前設置;

通過守護線程創建的線程會被自動設置為守護線程;

可以通過isDaemon方法來判斷一個線程是否是守護線程;

舉個守護線程的例子,代碼如下,當main線程運行結束後,所有的守護線程也被終止:

package concurrency;

import java.util.concurrent.*;

public class SimpleDaemons implements Runnable {
    public void run() {
        try {
            while (true) {
                TimeUnit.MILLISECONDS.sleep(100);
                System.out.println(Thread.currentThread() + " " + this);
            }
        } catch (InterruptedException e) {
            System.out.println("sleep() interrupted");
        }
    }

    public static void main(String[] args) throws Exception {
        for (int i = 0; i < 10; i++) {
            Thread daemon = new Thread(new SimpleDaemons());
            daemon.setDaemon(true); // Must call before start()
            daemon.start();
            
        }
        System.out.println("All daemons started");
        TimeUnit.MILLISECONDS.sleep(175);
    }
}

加入一個線程Thread.join方法

一個線程(T1)可以在其它線程(T2)之上調用join方法,結果是T1線程被掛起,等待T2線程執行完畢(T2.isAlive()==false),然後繼續執行T1線程;

也可以在join方法上加一個超時參數,保證join方法在指定時間內總能返回;

join方法可以被中斷,如調用T2.interrupt()方法,中斷後,join方法可以立即返回;

代碼實例:

package concurrency;

class Sleeper extends Thread {
    private int duration;

    public Sleeper(String name, int sleepTime) {
        super(name);
        duration = sleepTime;
        start();
    }

    public void run() {
        try {
            sleep(duration);
        } catch (InterruptedException e) {
            System.out.println(getName() + " was interrupted. "
                    + "isInterrupted(): " + isInterrupted());
            return;
        }
        System.out.println(getName() + " has awakened");
    }
}

class Joiner extends Thread {
    private Sleeper sleeper;

    public Joiner(String name, Sleeper sleeper) {
        super(name);
        this.sleeper = sleeper;
        start();
    }

    public void run() {
        try {
            sleeper.join();
        } catch (InterruptedException e) {
            System.out.println("Interrupted");
        }
        System.out.println(getName() + " join completed");
    }
}

public class Joining {
    public static void main(String[] args) {
        Sleeper sleepy = new Sleeper("Sleepy", 1500), grumpy = new Sleeper(
                "Grumpy", 1500);
        Joiner dopey = new Joiner("Dopey", sleepy), doc = new Joiner("Doc",
                grumpy);
        grumpy.interrupt();
        
        try {
            sleepy.join();
        } catch (InterruptedException e) {
            
            e.printStackTrace();
        }
        System.out.println("main thread continue until sleepy thread over");
    }
} 

在該示例中,我們把dopey、main線程加入到sleepy線程,doc線程加入到grumpy線程,結果如下:

grumpy線程被中斷,然後join方法立即返回,打印Doc join completed,在grumpy線程中,isInterrupted()之所以打印false是因為異常捕獲時把該標志清理了;

sleepy線程執行完畢後,join方法返回,繼續執行dopey線程和main線程未完成部分,打印“main thread continue until sleepy thread over”和“Dopey join completed”;

捕獲線程異常

在main方法中使用try-catch不能捕獲其它線程產生的異常,如下示例,RuntimeException未被處理:

package concurrency;

import java.util.concurrent.*;

public class ExceptionThread implements Runnable {
    public void run() {
        throw new RuntimeException();
    }

    public static void main(String[] args) {
        try {
            ExecutorService exec = Executors.newCachedThreadPool();
            exec.execute(new ExceptionThread());
        } catch (RuntimeException ue) {

            System.out.println("Exception has been handled!");
        }
    }
}

在JAVA SE5之前,可以使用線程組捕獲異常,在JAVA SE5之後可以用Executor來解決這個問題;

只需要寫一個異常處理類並實現Thread.UncaughtExceptionHandler接口,然後在創建線程的時候,設置該線程的未捕獲異常處理器為該類實例,通過setUncaughtExceptionHandler方法設置,如下代碼;

package concurrency; import java.util.concurrent.*; class ExceptionThread2 implements Runnable { public void run() { Thread t = Thread.currentThread(); System.out.println("run() by " + t); System.out.println("eh = " + t.getUncaughtExceptionHandler()); throw new RuntimeException(); } } class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread t, Throwable e) { System.out.println("caught " + e); } } class HandlerThreadFactory implements ThreadFactory { public Thread newThread(Runnable r) { System.out.println(this + " creating new Thread"); Thread t = new Thread(r); System.out.println("created " + t); t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler()); System.out.println("eh = " + t.getUncaughtExceptionHandler()); return t; } } public class CaptureUncaughtException { public static void main(String[] args) { ExecutorService exec = Executors .newCachedThreadPool(new HandlerThreadFactory()); exec.execute(new ExceptionThread2()); } } View Code

除了為每個線程設置專門的未捕獲異常處理器外,還可以設置默認的未捕獲異常處理器,當系統檢查到某個線程沒有專門的未捕獲異常處理器的時候,會使用默認的未捕獲異常處理器;

package concurrency;

import java.util.concurrent.*;

public class SettingDefaultHandler {
  public static void main(String[] args) {
    Thread.setDefaultUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
    ExecutorService exec = Executors.newCachedThreadPool();
    exec.execute(new ExceptionThread());
  }
} 

 

參考資料:JAVA編程思想-4

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved