非常詳細的JAVA併發基本線程機制

非常詳細的JAVA併發基本線程機制

編程問題中相當大的一部分都可以通過使用順序編程來解決。然而對於某些問題,若果能夠併發地執行程序中的多個部分,則會變得非常方便甚至非常必要,因爲這些部分要麼看起來在併發執行,要麼在多處理器環境下可以同時執行。

JAVA是一種多線程語言,並提出了併發問題。實現併發最直接的方式是在操作系統級別使用進程。進程是運行在它自己的地址空間內的自包容程序。多任務操作系統可以通過週期性地將CPU從一個進程切換到另一個進程,來實現同時運行多個進程。每個任務都作爲進程在其自己的地址空間中執行,因此它們是完全獨立的;更重要的事,對進程來說,它們之間沒有任何彼此通信的需要,因爲它們是完全獨立的。

JAVA在併發上採取了比較傳統的方式,在順序語言的基礎上提供對線程的支持。線程機制是在由執行程序表示的單一進程中創建任務。

在但CPU機器上使用多任務的程序在任意時刻仍舊值在執行一項工作,因此從理論上講,肯定可以不用任何很污二編寫出相同的程序。但是毛病發提供了一個重要的組織結構上的好處:你的程序設計可以極大地簡化。而例如仿真,沒有併發數的支持是很難解決的。

JAVA的線程機制是搶佔式的,這表示調度機制會週期性地中斷線程將上下文切換到另一個線程,從而爲每個線程都提供時間片,是的每個線程都會分配到數量合理的時間去驅動它的任務。

併發編程使我們可以將程序劃分爲多個分離的、獨立運行的任務。通過使用多線程機制,這些獨立任務(子任務)中的每一個都將有執行線程來驅動。一個線程就是在進程中的一個單一的順序控制流,因此,單個進程可以擁有多個併發執行的任務,但是你的程序使得每個任務都好像有其自己的CPU一樣,其底層機制是切分CPU時間(有需要了解這部分知識的朋友可以在下方留言,可以出一篇詳細的博客)。
CPU輪流給每個任務分配其佔用時。每個任務都覺得自己再一直在佔用CPU,大三事實上CPU時間是劃分成片段分配給了所有的任務(例外是程序確實運行在多個CPU之上)。線程的一大好處是可以使你從這個層次抽身出來,即代碼不必知道它是運行在具有一個還是多個CPU的機器上。所以,使用線程機制是一種建立透明的、可擴展的程序的方法。

 

JAVA多線程機制的代碼介紹篇章(實用篇)


1. 定義任務

package Kim_1;

/*定義任務,需要一種描述任務的方式,可由Runnable接口提供。
 * 想要定義任務,只需要實現Runnable接口並編寫run()方法,使得該任務可以執行你的命令
 * */

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 "#"+id+"("+(countDown > 0 ? countDown:"LiftOff!")+").";
    }

    public void run() {
        while(countDown-->0) {
            System.out.println(status());
            //    Thread.yield()方法作用是:暫停當前正在執行的線程對象,並執行其他線程;
            Thread.yield();
        }
    }
}

MAIN類:

package Kim_1;

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

猜猜運行結果是什麼勒?

 

 

 

分析對了嗎?沒對沒關係,跟我繼續往下,你就能漸漸理解了。
從結果我們可以得知,整個過程我們只有一個過程產生了ListOff對象,即目前只有0號一個LiftOff任務被執行;後面括號中的倒數進行發射爲描述的任務。

不過就納悶了說好的多線程呢?

的確,如果這裏我們需要實現多線程的話,還少了一些步驟;因爲雖然我們實現了Runnable接口,並重寫了run()函數,但是這個方法並無特殊之處——它不會產生任何內在的線程能力。要實現線程行爲,你必須顯示地講一個任務附着到線程上。

通過上面的程序我們可以再來理一遍上面的程序-任務-進程圖

(理解這個模式,之後就能很簡單的區分繼承自Thread和實現Runnable接口的區別了)

2. Thread類
將Runnable對象轉變爲工作任務的傳統方式是把它提交給一個Thread構造器
下面的例子展示瞭如何使用Thread來驅動LiftOff對象:

package Kim_1;

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

運行結果如下:


Thread構造器只需要一個Runnable對象。調用Thread對象的start()方法爲該線程執行必要的初始化操作,然後調用Runnble的run()方法,以便在這個新線程中啓動該任務。初學者看到這樣的結果可能會覺得很詫異,比較平時習慣了順序執行的方式;但這也就是多線程的魅力所在,實際上t.start()產生的是對LiftOff.run()的方法調用,並且這個方法還沒有完成,但是因爲LiftOff.run()是由不同的線程執行的,因此你人就可以執行main()線程中的其他操作。
到這裏不可避免還是一頭霧水,我們暫時先了解有這麼個東西,能運行出這樣奇怪的結果,具體細節咱們後面慢慢道來。

有了上面的基礎,你可以像方一樣很容易地添加更多的線程去驅動更多的任務,結果不方便截圖,各位小夥伴可以在自己的PC上運行一下試試,可以顯然的看到運行的結果雜亂無章,並且每次運行的結果幾乎都不一樣,這說明不同任務的執行在線程中被換進換出時混在了一起,這種交換是由線程條肚臍自動控制的,且線程調度機制是非確定性的。
(注意:本例子中,單一線程(main()在創建所以的LiftOff線程,所以id爲0-4。但是如果多個線程在創建LiftOff線程,那麼就要可能會有多個LiftOff擁有相同的id))
package Kim_1;

public class MoreBasicThreads {
    public static void main(String[] args) {
        for(int i=0;i<5;i++) 
            new Thread(new LiftOff()).start();
        System.out.println("Waiting for LiftOff");    
    }
}

當main()創建Thread對象時,它並可以捕獲任何對這些對象的引用。而每個Thread都“註冊”了它自己,因此有一個對它的引用,而且在它的任務結束退出其run()並死亡之前,垃圾回收期無法清除它。

3. 使用Executor(執行器)
Executor可以爲我們管理Thread對象,從而簡化併發編程;它在客戶端和任務之間提供了一個間接層;它允許我們管理一部任務的執行,而無需顯示地管理線程的聲明週期。
我們可以使用Excutor來代替MoreBasicThread.java中顯示地創建Thread對象。LiftOff對象知道如何運行具體的任務。ExecutorService知道如何構建恰當的上下文來執行Runnable對象。

package Kim_1;

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CachedThreadPool {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();//    ExecutorService是具有服務生命週期的Executor
        for (int i = 0; i < 5; i++) {
            exec.execute(new LiftOff());
        }
        exec.shutdown();//    可以防止新任務被提交給這個Executor
    }
}

這一次我們可以發現運行的結果齊整多了。

4. 從任務中產生返回值

package Kim_1;

/*從人物中產生返回值,
 * 通過實現Callable接口,Callable是一種具有類型參數的泛型,它的類型參數表示從方法call()中返回的值的類型
 * 並且必須使用ExcecutorService.submit()方法調用它*/

import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

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>> result = new ArrayList<Future<String>>();
        for (int i = 0; i < 10; i++) {
            result.add(exec.submit(new TaskWithResult(i)));
        }
        for (Future<String> fs : result) {
            try {
                System.out.println(fs.get());
            } catch (InterruptedException e) {
                // TODO: handle exception
                System.out.println(e);
                return;
            }catch (ExecutionException e) {
                // TODO: handle exception
                System.out.println(e);
            }finally {
                exec.shutdown();
            }
        }
    }
}

5. 休眠

package Kim_1;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class SleepingTask extends LiftOff{
    public void run() {
        try {
            while (countDown-->0) {
                System.out.print(status());
                TimeUnit.MILLISECONDS.sleep(1000);
            }
        } catch (Exception e) {
            // TODO: handle exception
            System.err.println("Interrupted");
        }
    }
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            exec.execute(new SleepingTask());
        }
        exec.shutdown();
    }
}

6. 優先級
線程的優先級將該線程的重要性傳給了調度器。調度器會傾向於讓優先級最高的線程限執行;但這並不意味着優先級低的線程得不到執行,優先級不會造成死鎖現象
三個常用的優先級級別常量:MAX_PRIORITY、NORM_PRIORITY、MIN_PRIORITY

package Kim_1;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/*
 * */
public class SimplePriorities implements Runnable{
    private int countDown = 5;
    private volatile double d;
    private int priority;
    public SimplePriorities(int priority){
        this.priority = priority;
    }
    //toString()方法被覆蓋,用來打印線程的名字
    //這裏爲自動生成的名稱(你也可以在構造器裏自己設置這個名稱)
    public String toString(){
        return Thread.currentThread()+":"+countDown;
    }
    public void run() {
        Thread.currentThread().setPriority(priority);
        while(true) {
            for (int i = 0; i < 100000; i++) {
                d+=(Math.PI + Math.E)/(double)i;
                if (i%1000==0) {
                    Thread.yield();
                }
                System.out.println(this);
                if (--countDown==0) {
                    return;
                }
            }
        }
    }
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) 
            exec.execute(new SimplePriorities(Thread.MIN_PRIORITY));
        exec.execute(new SimplePriorities(Thread.MAX_PRIORITY));
        exec.shutdown();

    }
}

7.讓步
yield()方法。當已經知道run()方法的循環的一次迭代過程中所需要的工作,就可以將線程調度機制一個暗示:你的工作已經做得差不多了,可以讓別的線程來使用CPU了。這個暗示將通過調用yield()方法來做出。當調用yield()時,你也是在建議具有相同優先級瑟其他線程可以運行。
注意:雖然yield()可以對線程的產生分佈給予了良好的處理機制,但是對於任何重要的控制或在調整應用時,都不能依賴於yield();

8.後臺線程
後臺線程是指在程序運行時在後臺提供的一種通用服務線程,並且這種線程並不屬於程序中不可或缺的部分,即當所有的非後臺程序結束時,程序也就終止了,同時會殺死進程中的所以後臺線程。比如:執行main()的就是一個非後臺線程。
必須在線程啓動之前調用setDaemon()方法,才能把它設置爲後臺線程。實例中因爲main()線程別設定爲短暫睡嗎,所以可以觀察到所以後臺線程啓動後的結果。
//: concurrency/SimpleDaemons.java
// Daemon threads don’t prevent the program from ending.

package Kim_1;

import java.util.concurrent.TimeUnit;

public class SimpleDaemons implements Runnable {
  public void run() {
try {
  while(true) {
TimeUnit.MILLISECONDS.sleep(100);
System.out.print(Thread.currentThread() + " " + this);
  }
} catch(InterruptedException e) {
    System.out.print("sleep() interrupted");
}
  }
  public static void main(String[] args) throws Exception {
for(int i = 0; i < 10; i++) {
  Thread daemon = new Thread(new SimpleDaemons());
  //    將daemon設爲後臺線程
  daemon.setDaemon(true); // Must call before start()
  daemon.start();
}
System.out.print("All daemons started");
TimeUnit.MILLISECONDS.sleep(175);
  }
} /* Output: (Sample)
All daemons started
Thread[Thread-0,5,main] SimpleDaemons@530daa
Thread[Thread-1,5,main] SimpleDaemons@a62fc3
Thread[Thread-2,5,main] SimpleDaemons@89ae9e
Thread[Thread-3,5,main] SimpleDaemons@1270b73
Thread[Thread-4,5,main] SimpleDaemons@60aeb0
Thread[Thread-5,5,main] SimpleDaemons@16caf43
Thread[Thread-6,5,main] SimpleDaemons@66848c
Thread[Thread-7,5,main] SimpleDaemons@8813f2
Thread[Thread-8,5,main] SimpleDaemons@1d58aae
Thread[Thread-9,5,main] SimpleDaemons@83cc67
...
*///:~

9.加入一個線程
一個線程可以在其他線程智商調用join()方法,其效果十等待一段時間知道第二個線程結束才能繼續執行。如果某個線程在另一個線程t上調用t.join(),此線程將被掛起,直到目標線程t結束才恢復(t.isAlive()爲false)。join()方法的調用可以被中斷,做法是在調用線程上調用interrupt()方法(追interrupt需要處理異常,用到try-catch語句塊)

從下方運行結果我們可以看出,Sleeper被中斷或者是正常結束,Joiner將和Sleeper一同結束。
//: concurrency/Joining.java
// Understanding join().
package Kim_1;

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();
  }
} /* Output:
Grumpy was interrupted. isInterrupted(): false
Doc join completed
Sleepy has awakened
Dopey join completed
*///:~
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章