Java并发编程(十一)经常使用工具

Java为开发提供了不少有用的工具类,这些工具类能够帮助咱们更加高效的编写并发程序,本篇咱们将介绍这些实用工具的用法。安全

ThreadLocal

ThreadLocal类用于解决多线程共享一个变量的问题,当多线程访问同一个变量时可能会致使结果的错误,防止这种错误第一种办法就是使用锁来保护对象;第二种方法就是完全根除共享,即每一个线程访问本身私有的变量。有的同窗会以为第二种方法就会有一些局限性,由于有些时候不得不共享同一个变量。是的确实有局限性,可是在不少状况下是能够不共享变量就能达到一样的效果,ThreadLocal就是为了解决这一问题而设计的。多线程

ThreadLocal使用方法以下:并发

class IncreaseThread implements Runnable {
    public void run() {
        for(int i=0; i< 10000; i++) {
            TLTest.number.set(TLTest.number.get() + 1);
        }
        //如下为汇总代码
        synchronized(TLTest.result) {
            TLTest.result += TLTest.number.get();
        }
    }
}
public class TLTest {
    public static ThreadLocal<Integer> number;
    public static Integer result = 0;
    public static void main(String[] args) throws Exception {
        number = new ThreadLocal<Integer>() {
            public Integer initialValue() {
                return 0;
            }
        };
        ExecutorService exec = Executors.newCachedThreadPool();
        exec.execute(new IncreaseThread());
        exec.execute(new IncreaseThread());
        exec.shutdown();
        Thread.sleep(500);
        System.out.println("result: " + result);
    }
}

输出结果以下:dom

result: 20000工具

在TLTest类中咱们定义了ThreadLocal变量和Integer变量,ThreadLocal变量须要为其建立一个匿名内部类来实现为其指定初始值,咱们将初始值指定为0。咱们定义了一个线程类,这个线程负责将ThreadLocal的值加10000,最后线程会将本身的计算结果汇总到TLTest.result变量中。这个过程当中虽然咱们建立的两个线程都对同一个ThreadLocal变量进行操做,可是没有致使计算结果出错。由于ThreadLocal为每个线程分配了不一样的存储空间,咱们能够简单的将其理解为一个线程对象和值的Map<Thread,Integer>(注意:只是能够这么理解,但实际上不是)。测试

CountDownLatch

CountDownLatch用于线程间的合做,其使用方法和wait()/notify()相似,CountDownLatch类有两个方法:countDown()和await()方法,在建立CountDownLatch的对象时为其指定countDown()方法调用的次数,当调用await()方法时当前线程会一直被阻塞,直到countDown()方法被调用了指定的次数。设想一种状况,一个工头在接到任务时会把任务分发给不一样的工人,只有当全部的工人都完成本身的工做的时候,工头才能够交工。this

咱们用代码模拟一下这种状况:spa

class Worker implements Runnable {
    private int id;
    public Worker(int id) {
                this.id = id;
    }
    public void run() {
        Random rand = new Random();
        int workTime = rand.nextInt(1000);
        System.out.println(id + ": 开始干活");
        try { Thread.sleep(workTime); } catch (Exception e) {}
        System.out.println(id + ": 完成了");
        CDLTest.cdl.countDown();
    }
}
public class CDLTest {
    private static int numberOfWorker = 3;
    public static CountDownLatch cdl = new CountDownLatch(numberOfWorker);
    public static void main(String[] args) throws Exception {
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i=0; i<numberOfWorker; i++) {
            exec.execute(new Worker(i));
        }
        exec.shutdown();
        cdl.await();
        System.out.println("工头:交工");
    }
}

输出结果以下:线程

1: 开始干活设计

2: 开始干活

0: 开始干活

1: 完成了

2: 完成了

0: 完成了

工头:交工

在本例中主线程承担工头的角色,调用await()方法等待工人线程完成工做。咱们还经过线程池建立了3个工人线程,咱们使用随机数让每一个线程随机睡眠0-1000毫秒,用来模拟工人工做。

每一个工人完成本身的任务后调用countDown()方法,当全部的工人线程都作完本身的工做后主线程就能够“交工”了。

PriorityBlockingQueue

PriorityBlockingQueue和前面讲过的LinkedBlockingQueue、ArrayBlockingQueue类似,他们都实现了BlockingQueue接口,可是PriorityBlockingQueue和它们最大的区别是这个队列每次取出的都是“优先级”最高的,而不是最早进入的。所以要想实现它的优先级的特性,容器中的元素必须实现了Comparable接口,不然容器将抛ClassCastException异常。此外PriorityBlockingQueue也是线程安全的,所以使用的时候不用加锁。因为以前咱们测试过LinkedBlockingQueue的阻塞性,所以PriorityBlockingQueue的阻塞性咱们就不测试了,简单的测试一下它的“优先级”的性质:

public class PBQTest {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> pbq = new PriorityBlockingQueue<String>();
        String[] strs = new String[]{"C", "A", "B"};
        for(int i=0;i <strs.length;i++) {
            pbq.add(strs[i]);
        }
        while(!pbq.isEmpty()) {
            System.out.println(pbq.take());
        }
    }
}

输出结果以下:

A

B

C

String类实现了Comparable接口,根据字母顺序比较字符串的大小。咱们向队列中添加元素的顺序是"C", "A", "B",而取出顺序是"A", "B", "C",此因能够看出其“优先级”的性质。

总结

本篇介绍了三个经常使用的工具类,ThreadLocal用于解决多线程共享同一个变量的问题,它至关于建立了一个以线程对象为key以目标对象为value的一个Map,但实际上和Map是有区别的,好比Map对象不会在某个线程退出后对相应的value作垃圾回收,而ThreadLocal会对其进行回收。CountDownLatch用于同步多个任务,让某些任务等待其它任务执行的一组操做,须要注意的是能够有多个线程调用await()方法,当调用countDown()的次数到达指定数量的时候全部调用await()方法的线程都会从阻塞状态变为运行状态。PriorityBlockingQueue的用法和其它实现BlockingQueue接口的用法类似,只是PriorityBlockingQueue中的元素的取出顺序是按照优先级排序的。

 公众号:今日说码。关注个人公众号,可查看连载文章。遇到不理解的问题,直接在公众号留言便可。

相关文章
相关标签/搜索