併發對學習編程的人來說可謂是一道坎,邁不過這道坎以後學習更加深奧的東西也只是學其皮毛,難有大成。我就是當時沒有打好基礎,最後學習Java web、Java框架的時候對這些東西很難有深刻的理解,所以我不得不回頭來惡補基礎。今天我們就來談談併發那些事。
併發
併發就是將程序劃分爲多個任務,每個任務可以在同一時間內同時執行,與順序執行相比大大縮短執行的時間。
在多處理機系統中情況確實如上所屬,所有的任務在同一時刻分發給不同的cpu執行,這確實可以大幅度提高程序的執行效率。
但是在單處理機系統中我們卻要換一個角度考慮併發。單處理機系統只有一個cpu,必然不能同時運行多個任務,這個時候任一時刻只能有一個任務在cpu中執行。這個時候的併發就是一個假象,它將一個cpu週期(cpu週期這個詞用的不準確)劃分爲多個時間間隙,每一個時間間隙只運行一個任務,當這個時間間隙過完後就會將當前的任務保存狀態保存,轉而執行下一任務,所有任務交替執行,這樣一來在這個週期內給人的感覺就是所有任務併發執行。
在單處理機系統下的這種交替執行從表面上看並不能提高任務的執行速度(與順序執行相比),因爲它要花費更多的時間用於上下文的切換,在一般情況下也確實如此。但是請你考慮這樣一種情況,如果這些任務中的一些任務要請求輸入(可能是其他io請求、網絡請求等)這時如果沒有輸入給出就會發生阻塞,導致下面的任務不能被執行,要知道程序的運行速度是遠比io的速度要快的多的,這個時候必然會浪費掉大量的等待時間。如果我們使用了多線程,遇到這種阻塞發生時調度程序就會將阻塞的線程移出cpu轉而執行其他的任務,在執行其他任務的時間裏等待阻塞的接觸。這樣看來,切換上下文所需要付出的代價還是值得的。
定義任務
線程驅動的對象是任務,我們要想使用多線程執行多任務,就必須先定義任務。常見的任務定義方式有三種:
- 實現Runnable接口
package com.mfs.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TaskWithRunnable {
public static void main(String[] args) {
// TODO Auto-generated method stub
//此處開啓五個任務,即開啓五個線程
for (int i = 0;i < 5; i ++) {
Thread t = new Thread(new Fb()); //調用Threa的構造器,將一個Ruannable對象轉換爲一個工作任務
t.start(); //調用start方法開始這個任務
}
System.out.println();
//除使用Thread構造器來創建線程任務外還可以使用Executor(執行器)創建線程池管理Thread對象
ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); //創建線程池,還有一些其他的線程池,大家可以試驗一下。
for (int i = 0; i < 5; i ++) { //向線程池中添加五個線程
cachedThreadPool.execute(new Fb()); //線程池會自動啓動線程
}
cachedThreadPool.shutdown(); //關閉線程池,線程池中已經有的線程繼續被執行,但是不能在添加新的線程進入線程池
}
}
class Fb implements Runnable {
private static int taskCount = 0;
private final int id = taskCount ++; //任務的id
@Override
public void run() { //實現Runable接口就必須實現此方法
// TODO Auto-generated method stub
int a = 0, b = 1;
for (int i = 0;i < 10; i ++) { //輸出斐波那契數列的前十個數字
System.out.print("#" + id + "(" + b + ") ");
int t = b;
b = a + b;
a = t;
Thread.yield(); //建議任務調度器在此處進行任務切換,但只是建議而已,是否切換並不確定,所以我們不能指望他完成任何重要的功能
}
}
}
- 實現Callable接口
Callable接口與Runnable接口的不同之處在於,Callable接口要實現的方法是call方法,並且此方法能夠產生返回值。
package com.mfs.thread;
import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class TaskWithCallable {
public static void main(String[] args) {
// TODO Auto-generated method stub
List<Future<String>> res = new ArrayList<>();
ExecutorService pool = Executors.newFixedThreadPool(5); //FixedThreadPool允許事先指定可以開啓的線程的最大值
for (int i = 0; i < 5; i ++) {
Future<String> future = pool.submit(new Fb1()); //返回一個Future對象
res.add(future);
}
/*
* 可以看到在我們已經添加了五個任務後仍然能夠在向pool中添加任務
* 但是這個任務卻不能立即執行,只有等pool中的一個任務終止之後才能啓動此任務
*/
res.add(pool.submit(new Fb1()));
pool.shutdown();
for (Future<String> f : res) {
try {
/*
* 調用get能夠返回對應任務的返回值。
* 如果此時對應任務還沒有執行完畢,返回值也就沒有準備完畢,此時會阻塞程序,直到返回值準備完成
* 我們也可以在get前使用isDone方法判斷是否準備完畢
* get方法也有指定超時等待的版本
*/
String s = f.get();
System.out.println(s);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
/*
*還是輸出斐波那契數列的前十位,不同的是要在10位數全部輸出後返回一個任務結束的字符串
*泛型指定返回值的類型
*/
class Fb1 implements Callable<String> {
private static int taskCount = 0;
private final int id = taskCount ++;
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
int a = 0, b = 1;
for (int i = 0; i < 10; i ++) {
System.out.print("#" + id + "(" + b + ") ");
int t = b;
b = a + b;
a = t;
}
return "#" + id + "任務結束";
}
}
- 繼承Thread類
這種方法實現人物的定義比較簡單,但我們的任務不是很複雜是確實是一個行之有效的方法。但是繼承的侷限性我們都知道,所以一般來說是不太常用這種方法的。
package com.mfs.thread;
public class TaskWithThread {
public static void main(String[] args) {
// TODO Auto-generated method stub
for (int i = 0; i < 5; i ++) {
new Fb2().start(); //直接創建對象調用Threa就好了,不能使用線程池
}
}
}
class Fb2 extends Thread {
private static int taskCount = 0;
private final int id = taskCount ++;
@Override
public void run() {
// TODO Auto-generated method stub
int a = 0, b = 1;
for (int i = 0; i < 10; i ++) {
System.out.print("#" + id + "(" + b + ") ");
int t = b;
b = a + b;
a = t;
}
}
}