Java多线程实践—篇外篇
2015-08-21 13:20
771 查看
前面写了5篇关于多线程的知识点,看了一些面试题,发现是有遗漏,补充一下!另外多线程复杂多变,多练习、多看牛人代码才好!
首先得修改Executor产生线程的方式(默认是不支持捕获异常的)。Thread.UncaughtExceptionHandler(Java SE5新接口),允许在每个Thread对象上附着一个异常处理器。为了使用它,创建一个新类型ThreadFactory,它将在每个新创建的Thread对象上附着Thread.UncaughtExceptionHandler。然后将这个工厂传递给Executors创建的ExecutorService。下面是代码:
代码中的count.increment()会增加失败的可能性,虽然可能性很小!虽然yield和sleep是不释放锁的,++temp虽然在synchronized块中,但也不是原子性操作!在实际coding中要尽量避免这类问题!
Chopstick.java
这是offer的(简单点,只看offer和put)
提示:在使用类似的并发集合时,一定要注意选择方法(先去看看API)!
1. 先说说捕获异常
由于线程的本质特性,使得不能捕获从线程中逃逸的异常。一旦异常逃出任务的run()方法,就会传播到控制台,除非用特殊的手段捕获这种错误异常。在Java SE5之前用线程组来捕获异常,Java SE5之后可以用Executor来解决。《Think in Java》用了8行字来介绍线程组,告诉读者:这就是个失败的作品!下面来看看一般情况下的异常抛出:package zy.thread.demo; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class ExceptionThread implements Runnable{ public void run() { throw new RuntimeException(); } public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); service.execute(new ExceptionThread()); } }运行可以看到:
Exception in thread "pool-1-thread-1" java.lang.RuntimeException at zy.thread.demo.ExceptionThread.run(ExceptionThread.java:8) at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) at java.lang.Thread.run(Unknown Source)将main中的代码放入try-catch块中也没有用。Executor可以解决问题。
首先得修改Executor产生线程的方式(默认是不支持捕获异常的)。Thread.UncaughtExceptionHandler(Java SE5新接口),允许在每个Thread对象上附着一个异常处理器。为了使用它,创建一个新类型ThreadFactory,它将在每个新创建的Thread对象上附着Thread.UncaughtExceptionHandler。然后将这个工厂传递给Executors创建的ExecutorService。下面是代码:
package zy.thread.demo; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; public class CaptureUncaughtException { public static void main(String[] args) { //设置默认的未捕获异常处理器 //Thread.setDefaultUncaughtExceptionHandler(new MyExceptionHandler()); ExecutorService executor = Executors.newFixedThreadPool(1, new MyHandlerFactory()); executor.execute(new ExceptionThread2()); } } class ExceptionThread2 implements Runnable { public void run() { Thread t = Thread.currentThread(); System.out.println("run() by " + t); System.out.println("eh = " + t.getUncaughtExceptionHandler()); throw new RuntimeException(); } } class MyExceptionHandler implements Thread.UncaughtExceptionHandler { public void uncaughtException(Thread t, Throwable e) { System.out.println("caught " + e); } } class MyHandlerFactory implements ThreadFactory { public Thread newThread(Runnable r) { System.out.println(this + " creating new Thread"); Thread thread = new Thread(r); System.out.println("created " + thread); //如果setUncaughtExceptionHandler没有参数,则默认会用线程组来捕获 thread.setUncaughtExceptionHandler(new MyExceptionHandler()); System.out.println("eh = " + thread.getUncaughtExceptionHandler()); return thread; } }
2. 示例:“装饰性花园”
展示了如何用volatile布尔变量终止任务以及资源共享:一个花园有四扇门,现在花园委员会希望可以统计每天进入公园的人数,每个门都有一个计数器。package zy.thread.demo; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class OrnamentalGarden { public static void main(String[] args) throws InterruptedException { ExecutorService executor = Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++) executor.execute(new Entrance(i)); TimeUnit.SECONDS.sleep(1); Entrance.cancel(); executor.shutdown(); //如果在规定时间内所有任务都执行完毕,则返回true。否则返回false if(!executor.awaitTermination(250, TimeUnit.MILLISECONDS)) System.out.println("Some tasks were not terminated!"); System.out.println("Total: " + Entrance.getTotalCount()); System.out.println("Sum of Entrance " + Entrance.sumEntrance()); } } class Count { private int count = 0; // private Random rand = new Random(); public synchronized int increment() { // int temp = count; // if(rand.nextBoolean()) // Thread.yield(); // return (count = ++temp); return ++count; } public synchronized int value() { return count; } } class Entrance implements Runnable { private static Count count = new Count(); private final int id; private static List<Entrance> list = new ArrayList<>(); private int number = 0; private static volatile boolean canceled = false; public static void cancel() { canceled = true; } public Entrance(int id) { this.id = id; list.add(this); } public void run() { while (!canceled) { synchronized (this) { ++number; } System.out.println(this + " : Total " + count.increment()); try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Stopping " + this); } public synchronized int getValue() { return number; } public String toString() { return "Entrance " + id + ": " + getValue(); } public static int getTotalCount() { return count.value(); } public static int sumEntrance() { int sum = 0; for (Entrance entrance : list) { sum += entrance.getValue(); } return sum; } }
代码中的count.increment()会增加失败的可能性,虽然可能性很小!虽然yield和sleep是不释放锁的,++temp虽然在synchronized块中,但也不是原子性操作!在实际coding中要尽量避免这类问题!
3. 本地线程
ThreadLocal类更像是一个变量,它为每一个线程存储一个值,因此根除了资源的竞争,不会出现竞争条件。ThreadLocal通常当做静态域存储,只能通过get()、set()方法来访问该对象内容。我们用ThreadLocal来重写上面的花园计数问题:package zy.thread.demo; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; public class MyThreadLocal { private static ThreadLocal<Integer> value = new ThreadLocal<Integer>() { protected synchronized Integer initialValue() { return 0; } }; public static void increment() { value.set(value.get() + 1); } public static int get() { return value.get(); } public static void main(String[] args) throws InterruptedException { ExecutorService exec = Executors.newCachedThreadPool(); for(int i = 0; i < 5; ++i) exec.execute(new MyEntrance(i)); TimeUnit.SECONDS.sleep(3); MyEntrance.cancel(); exec.shutdown(); if(!exec.awaitTermination(250, TimeUnit.MILLISECONDS)) System.out.println("Some tasks were not terminated!"); } } class MyEntrance implements Runnable { private final int id; private int number = 0; private static volatile boolean canceled = false; public static void cancel() { canceled = true; } public MyEntrance(int id) { this.id = id; } public void run() { while (!canceled) { synchronized (this) { ++number; } MyThreadLocal.increment(); try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Stopping " + this); System.out.println(Thread.currentThread() + " Local " + MyThreadLocal.get()); } public synchronized int getValue() { return number; } public String toString() { return "Entrance " + id + ": " + getValue(); } }注意:MyEntrance的编号是从0开始,而Thread的编号是从1开始的,在比较结果是否正确的时候,请注意!
Stopping Entrance 2: 10 Thread[pool-1-thread-3,5,main] Local 10 Stopping Entrance 1: 10 Thread[pool-1-thread-2,5,main] Local 10 Stopping Entrance 0: 10 Thread[pool-1-thread-1,5,main] Local 10 Stopping Entrance 4: 10 Thread[pool-1-thread-5,5,main] Local 10 Stopping Entrance 3: 10 Thread[pool-1-thread-4,5,main] Local 10
4. 死锁
看一个经典的例子:哲学家进餐问题。这个例子有两个参数很重要,哲学家思考的时间(ponder)、哲学家的数量(size),当ponder比较大或者size比较大时,表示他们花更多时间去思考,尽管存在死锁的可能,但是可能永远看不到死锁!当把ponder设置为0时,死锁很快就发生。下面是不会产生死锁的版本:Chopstick.java
package zy.thread.demo; public class Chopstick { private boolean taken = false; public synchronized void take() throws InterruptedException { while(taken) wait(); taken = true; } public synchronized void drop() { taken = false; notifyAll(); } }Philosopher.java
package zy.thread.demo; import java.util.Random; import java.util.concurrent.TimeUnit; public class Philosopher implements Runnable { private Chopstick right; private Chopstick left; private final int id; private final int ponderFactor; private Random rand = new Random(47); private void pause() throws InterruptedException { if (ponderFactor == 0) return; TimeUnit.MILLISECONDS.sleep(rand.nextInt(ponderFactor * 250)); } public Philosopher(Chopstick right, Chopstick left, int id, int ponder) { this.right = right; this.left = left; this.id = id; this.ponderFactor = ponder; } public void run() { try { while (!Thread.interrupted()) { System.out.println(this + " " + "thinking"); pause(); System.out.println(this + " " + "grabbing right"); right.take(); System.out.println(this + " " + "grabbing left"); left.take(); System.out.println(this + " " + "eating"); pause(); right.drop(); left.drop(); } } catch (InterruptedException e) { e.printStackTrace(); } } public String toString() { return "Philosopher " + id; } }DeadlockingDiningPhilosophers.java
package zy.thread.demo; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class DeadlockingDiningPhilosophers { public static void main(String[] args) { int ponder = 0; int size = 5; ExecutorService service = Executors.newCachedThreadPool(); Chopstick[] sticks = new Chopstick[size]; for (int i = 0; i < size; i++) sticks[i] = new Chopstick(); for (int i = 0; i < size; i++) if (i < size - 1) service.execute(new Philosopher( sticks[i], sticks[i + 1], i, ponder)); else service.execute(new Philosopher( sticks[0], sticks[i], i, ponder)); service.shutdown(); } }会产生死锁的版本只要修改DeadlockingDiningPhilosophers.java的第12行的for循环主体部分
service.execute(new Philosopher(sticks[i], sticks[(i + 1) % size], i, ponder));
5. 最后来看看Java是如何实现阻塞队列的(以ArrayBlockingQueue为例)
当我用offer()和poll()操作时,发现并不是线程安全的,而put()和take()发现却是线程安全的,这是为什么呢?来看看源码:这是offer的(简单点,只看offer和put)
public boolean offer(E e) { checkNotNull(e); final ReentrantLock lock = this.lock; lock.lock(); try { if (count == items.length) return false; else { insert(e); return true; } } finally { lock.unlock(); } }当发现队列慢的时候,直接返回false了,并没有等待,因此会出现不同步的现象,就像注释里说的
提示:在使用类似的并发集合时,一定要注意选择方法(先去看看API)!
相关文章推荐
- Eclipse生成的java class文件通过java命令行调用提示Exception in thread "main" java.lang.NoClassDefFoundError
- Eclipse生成的java class文件通过java命令行调用提示找不到主类的问题
- JDK API 在线文档
- java初始化块
- Eclipse Import Projects from Git
- java中的中文字符串排序
- Java性能优化(6):避免使用终结函数
- java 内存泄漏
- Java数组拷贝
- 20_ java.lang.IllegalArgumentException: Service Intent must be explicit异常说明
- Java中的集合Collection以及对应的子类list类
- Spring中bean的配置
- JavaDoc支持的标签
- 排序算法系列——基数排序
- java内部类,局部变量加Final分析
- Java进阶02 异常处理
- Eclipse常规设置(我的Style我做主)
- SpringMVC 使用poi导入导出Excel
- xjc命令转换成java类乱码
- 将java源码打成jar包