分享几个写并发题遇到的坑
16lz
2021-01-22
最近部门领导出了些 Java 并发相关的题目,让我们业余做,提高技术。
我做了题目,第一个提交答案,没想到误导了一帮兄弟。哈哈,原来我才是最大的坑!当然,坑了同事,不能坑大家。
坑 1
启动多个线程为了等待任务结果,使用了 Thread 类的 join 方法,源码如下
long s1 = System.currentfor (int i = 0; i < 100; i++) { Thread t = new Thread(); t.start(); t.join();}
问题就出在了第 5 行,启动完线程,就等待结果,阻塞了后面线程的启动。
坑 2
ThreadPoolExecutor 使用第一个构造函数
//缓冲任务队列
private ArrayBlockingQueue<Runnable> taskQueue = new ArrayBlockingQueue<>(5);
private static final int corePoolSize = 2; //核心线程数
private static final int maximumPoolSize = 5; //最大线程数
private static final int keepAliveTime = 0; //当前线程数大于核心线程数时,多余空闲线程在终止前等待新任务的最长时间
private ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, taskQueue);
这个构造函数默认 AbortPolicy 拒绝策略,然后领导的电脑执行报错,任务被拒绝。在生产环境一定要考虑清楚线程池的拒绝策略,尤其是对任务的丢弃,会不会造成很大影响,任务提交状态和执行状态一定要有监控。不然最后就是失控状态...
遂,加了任务提交失败的记录与重新提交。
private List<FactorialCalculator> rejectedTasks = new ArrayList<>();
try {
Future<Long> future = executor.submit(task);
futures.add(future)
} catch (RejectedExecutionException e) {
System.out.println("task cal factorial " + i + "! task be rejected");
rejectedTasks.add(task);
}
List<Future<Long>> futures = new ArrayList<>(10);
Iterator<FactorialCalculator> iterator = rejectedTasks.iterator();
while (iterator.hasNext()) {
FactorialCalculator task = iterator.next();
Thread.sleep(delay);
try {
Future<Long> future = executor.submit(task);
futures.add(future);
iterator.remove();
System.out.println("rejected task : " + task + "重新提交成功");
} catch (RejectedExecutionException e) {
}
}
坑 3、
类似坑 1,实际在串行,还以为在并发。
for (int i = 1; i <= num; i++) { FactorialCalculator task = new FactorialCalculator(i); Future<Long> future = executor.submit(task); result += future.get();}
坑 4、
使用了 AtomicLongFieldUpdater 的 compareAndSet 方法,以为此方法会自旋到更新成功,其实是想用 incrementAndGet 方法。使用 compareAndSet 方法需要自己控制自旋更新值。
private volatile long counter = 0L;
private static AtomicLongFieldUpdater<Counter> updater = AtomicLongFieldUpdater.newUpdater(Counter.class, "counter");
/**
* 增加1
*/
public void increase() {
updater.incrementAndGet(this);
}
/**
* 增加指定数值
*/
public void increase(long delta) {
while (!updater.compareAndSet(this, this.counter, this.counter + delta)) {
}
}
有句玩笑话是:解决并发问题最好的办法就是避免写并发代码。
可见并发代码,极容易出错,而且代码有了问题,自己很难看出。有时候结果是正确的,发现性能很差,原来代码是在串行执行,还另启线程浪费资源。有时候大部分时间结果是正确的,但总会偶尔来几个问题,诡异的出现又诡异的消失,很难追踪定位。
这也许就是并发编程的魅力。
PS:
期待被灰度到公众号的「问答」功能,听听大家的声音。
更多相关文章
- 模板方法模式在开源代码中应用
- 接了烂代码的项目,怎么玩好?
- 组合模式在开源代码中的应用
- 享元模式在开源代码中的应用
- 多线程三分钟就可以入个门了!
- 外观模式在开源代码中的应用
- 装饰器模式在开源代码中的应用
- 适配器模式在开源代码中的应用
- 建造者模式和原型模式在开源代码中的应用