1. 程式人生 > >並發工具

並發工具

main ger pic test ++ pan fin pro 青年

Fork/Join框架

  在JDK7後提供一套並行任務的框架,它可以把發大任務拆分成很多的小任務,匯總每個小任務的結果得到大任務的結果。

工作竊取算法

工作竊取(work-stealing)算法是指某個線程從其他隊列裏竊取任務。

那麽,為什麽需要使用竊取算法呢?假如我們需要做一個比較大的任務,可以把這個任務分割為若幹互不依賴的子任務,為了減少線程間的競爭,把這些子任務粉筆放到不同的隊列裏,並為每個隊列創建一個單獨的線程來執行隊列裏的任務,線程和隊列一一對應。

比如A線程負責處理A隊列裏的任務,但是有的線程會先把自己隊列裏的任務幹完,而其他線程對應的隊列裏還有任務等待處理。幹完活的線程與其等著,不如去幫其他線程幹活,於是它就去其他線程的隊列裏竊取一個任務來執行。而在這時它們會訪問同一個隊列,所以為了減少竊取任務線程的和竊取任務線程之間的競爭,通常會使用雙端隊列,被竊取任務線程永遠從雙端隊列的頭部拿任務執行,而竊取任務的線程永遠從雙端隊列的尾部拿任務執行。

由兩個類完成工作:

ForkJoinTask:我們要使用ForkJoin框架,必須首先創建一個ForkJoin任務。它提供在任務中執行fork()和join()操作的機制。通常情況下,我們不需要直接繼承ForkJoin類,只需要繼承它的子類,Fork/Join框架提供了以下兩個子類。

  • RecursiveAction:用於沒有返回結果的任務
  • RecursiveTask:用於有返回結果的任務

ForkJoinPool:ForkJoinTask需要通過ForkJoinPool來執行。

Fork/Join有同步和異步兩種方式。

舉個例子:

青年高智商訓練班開始招生了,此時來了5萬名小夥伴參與報名篩選,但只有年齡在18~30歲之間,並且智商超過145的人才能通過。為了提高效率,每位老師最多測試不超過100名參與者,最終將通過人數進行統計。

// 參與者
public class Young {

    private Integer age;
    private String name;
    private Integer iq;
    public Young(Integer age, String name, Integer iq) {
        this.age = age;
        this.name = name;
        this.iq = iq;
    }
    public Integer getAge() {
        return age;
    }
    public
void setAge(Integer age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getIq() { return iq; } public void setIq(Integer iq) { this.iq = iq; } }
// 篩選接口
public interface Pick<T> {
    boolean pick(T person);
}
// 考題
public class Exam implements Pick<Young> {
    
    @Override
    public boolean pick(Young person) {
        if (30 > person.getAge() && 18 < person.getAge() && 145 < person.getIq()) {
            return true;
        }
        return false;
    }

}
// 監考老師
public class Teacher extends RecursiveTask<Integer> {

    private final Integer THRESHOLD = 100;
    
    private List<Young> youngs;
    private Integer fromIdx;
    private Integer toIdx;
    private Pick pick;
    
    private Integer count = 0;
    
    public Teacher(List<Young> youngs, Integer fromIdx, Integer toIdx, Pick pick) {
        this.youngs = youngs;
        this.fromIdx = fromIdx;
        this.toIdx = toIdx;
        this.pick = pick;
    }

    @Override
    protected Integer compute() {
        if (this.toIdx - this.fromIdx < this.THRESHOLD) {
            for (int i = this.fromIdx; i < this.toIdx; i ++) {
                if (this.pick.pick(this.youngs.get(i))) {
                    this.count ++;
                }
            }
            return this.count;
        } else {
            Integer mid = (this.toIdx - this.fromIdx) / 2;
            Teacher teacher1 = new Teacher(this.youngs, this.fromIdx, this.fromIdx + mid, this.pick);
            Teacher teacher2 = new Teacher(this.youngs, this.fromIdx + mid, this.toIdx, this.pick);
            this.invokeAll(teacher1, teacher2);
            return teacher1.join() + teacher2.join();
        }
    }
}
public class Test {
    
    private static Integer COUNT = 50000;
    
    // 所有參與者
    public static List<Young> makeData() {
        List<Young> list = new LinkedList<>();
        Random rand = new Random();
        Exam exam = new Exam();
        Young young;
        int cnt = 0;
        for (int i = 1; i <= COUNT; i ++) {
            young = new Young(rand.nextInt(50), "參與者" + i, rand.nextInt(100) + 100);
            list.add(young);
            if (exam.pick(young)) {
                cnt ++;
            }
        }
        System.out.println("共有" + cnt + "參與者符合條件");
        return list;
    }

    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool();
        Teacher teacher = new Teacher(makeData(), 0, COUNT - 1, new Exam());
        
        pool.invoke(teacher);
        
        System.out.println("Fork/Join計算結果:" + teacher.join());
    }
}

輸出:

共有6037參與者符合條件
Fork/Join計算結果:6037

可以看出兩次結果是一致的。

並發工具