标签:文章 线程池 next code this his 存储 并发编程 exec
Java为开发提供了很多有用的工具类,这些工具类可以帮助我们更加高效的编写并发程序,本篇我们将介绍这些实用工具的用法。
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); } }
输出结果如下:
result: 20000
在TLTest类中我们定义了ThreadLocal变量和Integer变量,ThreadLocal变量需要为其创建一个匿名内部类来实现为其指定初始值,我们将初始值指定为0。我们定义了一个线程类,这个线程负责将ThreadLocal的值加10000,最后线程会将自己的计算结果汇总到TLTest.result变量中。这个过程中虽然我们创建的两个线程都对同一个ThreadLocal变量进行操作,但是没有导致计算结果出错。因为ThreadLocal为每一个线程分配了不同的存储空间,我们可以简单的将其理解为一个线程对象和值的Map<Thread,Integer>(注意:只是可以这么理解,但实际上不是)。
CountDownLatch用于线程间的合作,其使用方法和wait()/notify()类似,CountDownLatch类有两个方法:countDown()和await()方法,在创建CountDownLatch的对象时为其指定countDown()方法调用的次数,当调用await()方法时当前线程会一直被阻塞,直到countDown()方法被调用了指定的次数。设想一种情况,一个工头在接到任务时会把任务分发给不同的工人,只有当所有的工人都完成自己的工作的时候,工头才可以交工。
我们用代码模拟一下这种情况:
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和前面讲过的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中的元素的取出顺序是按照优先级排序的。
公众号:今日说码。关注我的公众号,可查看连载文章。遇到不理解的问题,直接在公众号留言即可。
标签:文章 线程池 next code this his 存储 并发编程 exec
原文地址:https://www.cnblogs.com/victorwux/p/9218364.html