java并发工具

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

可以看出两次结果是一致的。

 

CountDownLatch

CountDownLatch是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。

 

public class CountDownLatchTest {

    private static CountDownLatch cdl = new CountDownLatch(7);
    
    private static class SubThread implements Runnable {

        @Override
        public void run() {
            System.out.println(Thread.currentThread().getId());
            cdl.countDown();
            System.out.println(Thread.currentThread().getId() + " is done");
        }
        
    }
    
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getId());
                cdl.countDown();
                System.out.println("sleeping...");
                try {
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("sleep is completer");
                cdl.countDown();
            }
        }).start();
        
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(new SubThread());
            thread.start();
        }
        
        cdl.await();
        System.out.println("Main will gone...");
    }
}

 输出:

11
11 is done
10
sleeping...
14
14 is done
12
12 is done
13
13 is done
15
15 is done
sleep is completer
Main will gone...

 

CyclicBarrier

CyclicBarrier与CountDownLatch类似

public class CyclicBarrierTest {

    private static CyclicBarrier cb = new CyclicBarrier(5, new SubThread());
    
    private static ConcurrentHashMap<String, Integer> resultMap = new ConcurrentHashMap<>();
    
    private static class SubThread implements Runnable {

        @Override
        public void run() {
            int result = 0;
            for (Map.Entry<String, Integer> workResult : resultMap.entrySet()) {
                result = result + workResult.getValue();
            }
            System.out.println("result = " + result);
        }
    }
    
    private static class WorkThread implements Runnable {

        private Random rand = new Random();
        
        @Override
        public void run() {
            int r = rand.nextInt(1000) + 1000;
            System.out.println(Thread.currentThread().getId() + ":r = " + r);
            resultMap.put(Thread.currentThread().getId() + "", r);
            try {
                Thread.sleep(1000 + r);
                cb.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        for (int i = 0; i < 5; i ++) {
            Thread thread = new Thread(new WorkThread());
            thread.start();
        }
    }
}

输出:

10:r = 1008
12:r = 1624
11:r = 1198
13:r = 1245
14:r = 1180
result = 6255

这里当WorkThread的第五个线程到达await()方法时触发执行SubThread线程。

 

CountDownLatch与CyclicBarrier比较

CountDownLatch CyclicBarrier
减计数方式 加计数方式
计算为0时释放所有等待的线程 计数达到指定值时释放所有等待线程
调用countDown()方法计数减1,调用await()方法只进行阻塞,对计数没任何影响 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞
不可重复利用 可重复利用

 

 

 

 

 

 

 

 

Semaphore

Semaphore是计数信号量。Semaphore管理一系列许可证。每个acquire方法阻塞,直到有一个许可证可以获得然后拿走一个许可证;每个release方法增加一个许可证,这可能会释放一个阻塞的acquire方法。然而,其实并没有实际的许可证这个对象,Semaphore只是维持了一个可获得许可证的数量。

public class SemaphoreTest<T> {

    private final Semaphore items;
    private final Semaphore space;
    private List<T> queue = new LinkedList<>();
    
    public SemaphoreTest(int itemCnt) {
        this.items = new Semaphore(0);
        this.space = new Semaphore(itemCnt);
    }
    
    // 存放数据
    public void put(T v) throws InterruptedException {
        this.space.acquire();    // 拿空位的许可,没有空位线程会在这个方法上阻塞
        synchronized (this.queue) {
            this.queue.add(v);
        }
        this.items.release();    // 有元素了,可以释放一个拿元素的许可
    }
    
    // 读取数据
    public T take() throws InterruptedException {
        this.items.acquire();    // 拿元素的许可,没有元素许可线程会在这个方法上阻塞
        T v;
        synchronized (this.queue) {
            v = this.queue.remove(0);
        }
        this.space.release();    // 有空位了,可以释放一个存在空位的许可
        return v;
    }
}

 

Exchanger

它可以在两个线程之间传输数据

public class ExchangerTest {

    private static final Exchanger<List<String>> exgr = new Exchanger<>();
    
    public static void main(String[] args) {
        new Thread(new Runnable() {
            
            @Override
            public void run() {
                try {
                    List<String> list = new ArrayList<>();
                    list.add(Thread.currentThread().getId() + " insert A1");
                    list.add(Thread.currentThread().getId() + " insert A2");
                    list = exgr.exchange(list);    // 交换数据
                    for (String item : list) {
                        System.out.println(Thread.currentThread().getId() + ":" + item);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(new Runnable() {
            
            @Override
            public void run() {
                try {
                    List<String> list = new ArrayList<>();
                    list.add(Thread.currentThread().getId() + " insert B1");
                    list.add(Thread.currentThread().getId() + " insert B2");
                    list.add(Thread.currentThread().getId() + " insert B3");
                    System.out.println(Thread.currentThread().getId() + " will sleep");
                    Thread.sleep(1500);
                    list = exgr.exchange(list);    // 交换数据
                    for (String item : list) {
                        System.out.println(Thread.currentThread().getId() + ":" + item);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

输出:

11 will sleep
11:10 insert A1
10:11 insert B1
11:10 insert A2
10:11 insert B2
10:11 insert B3

这里可以看出线程10和11的list内容做了交换。

posted @ 2018-01-13 17:29  huanStephen  阅读(282)  评论(0编辑  收藏  举报