1、使用线程的一些经验
设置名称
无论何种方式,启动一个线程,就要给它一个名字!这对排错诊断系统监控有帮助。否则诊断问题时,无法直观知道某个线程的用途。
响应中断
程序应该对线程中断作出恰当的响应。
使用ThreadLocal
它的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有该变量。
注意:使用ThreadLocal,一般都是声明在静态变量中,如果不断的创建ThreadLocal而且没有调用其remove方法,将会导致内存泄露。
2、Executor :ExecutorService和Future
为了方便并发执行任务,出现了一种专门用来执行任务的实现,也就是Executor。由此,任务提交者不需要再创建管理线程,使用更方便,也减少了开销。
java.util.concurrent.Executors是Executor的工厂类,通过Executors可以创建你所需要的Executor。
ExecutorService executor = Executors.newSingleThreadExecutor();
Callable<Object> task = new Callable < Object > () {
public Object call () throws Exception {
Object result = "...";
return result;
}
} ;
Future<Object> future = executor.submit(task);
future.get();
有两种任务:Runnable、Callable,Callable是需要返回值的任务。
Task Submitter把任务提交给Executor执行,他们之间需要一种通讯手段,这种手段的具体实现,通常叫做Future。Future通常包括get(阻塞至任务完成),cancel,get(timeout)(等待一段时间)等等。Future也用于异步变同步的场景。
3、阻塞队列: put和take、offer和poll、drainTo
阻塞队列,是一种常用的并发数据结构,常用于生产者-消费者模式。
在Java中,有三种常用的阻塞队列:
ArrayBlockingQueue
LinkedBlockingQueue
SynchronousQueue
使用阻塞队列:
Monitor的理论模型
4、ReentrantLock和Synchronized
Synchronized是Lock的一种简化实现,一个Lock可以对应多个Condition,而synchronized把Lock和Condition合并了,一个synchronizedLock只对应一个Condition,可以说Synchronized是Lock的简化版本。
在JDK5,Synchronized要比Lock慢很多,但是在JDK6中,它们的效率差不多。
5、Lock-free算法
LockFree算法,不需要加锁。
通常都是三个部分组成:
①循环
②CAS (CompareAndSet)
③break
进一步使用Lock-Free数据结构
class BeanManager {
private ConcurrentMap<String, Object> map = new ConcurrentHashMap<String, Object>();
public Object getBean
(String key) {
Object bean = map.get(key);
if (bean == null) {
map.putIfAbsent(key, createBean());
bean = map.get(key);
}
return bean;
}
}
ConcurrentHashMap并没有实现Lock-Free,只是使用了分离锁的办法使得能够支持多个Writer并发。ConcurrentHashMap需要使用更多的内存。
同样的思路用于更新数据库-乐观锁。
Lock-Free算法,可以说是乐观锁,如果非激烈竞争的时候,不需要使用锁,从而开销更小,速度更快。
适当使用CopyOnWriteArrayList,能够提高读操作时的效率。
6、关于锁使用的经验介绍
使用支持CAS的数据结构,避免使用锁,如:AtomicXXX、ConcurrentMap、CopyOnWriteList、ConcurrentLinkedQueue
一定要使用锁的时候,注意获得锁的顺序,相反顺序获得锁,就容易产生死锁。
死锁经常是无法完全避免的,鸵鸟策略被很多基础框架所采用。
通过Dump线程的StackTrace,例如linux下执行命令kill -3 <pid>,或者jstack–l <pid>,或者使用Jconsole连接上去查看线程的StackTrace,由此来诊断死锁问题。
外部锁常被忽视而导致死锁,例如数据库的锁
7、并发流程控制手段:CountDownlatch、Barrier
并发流程控制-使用CoutDownLatch
final int COUNT = 10;
final CountDownLatch completeLatch = new CountDownLatch(COUNT);
for (int i = 0; i < COUNT; ++i) {
Thread thread = new Thread("worker thread " + i) {
public void run() {
// do xxxx
completeLatch.countDown();
}
};
thread.start();
}
// 当你启动了一个线程,你需要等它执行结束,此时,CountDownLatch也许是一个很好的选择。
completeLatch.await();
final CountDownLatch startLatch = new CountDownLatch(1);
for (int i = 0; i < 10; ++i) {
Thread thread = new Thread("worker thread " + i) {
public void run() {
try {
// 当你启动很多线程,你需要这些线程等到通知后才真正开始,CountDownLatch也许是一个很好的选择。
startLatch.await();
} catch (InterruptedException e) {
return;
}
// do xxxx
}
};
thread.start();
}
// do xxx
startLatch.countDown();
Barrier:
屏障是一种协调机制(一种算法),它迫使参与并发(或分布式)算法的进程等待,直到它们中的每一个都达到其程序中的某个点。这些协调点的集合被称为屏障。一旦所有流程都达到了障碍,它们都可以继续通过障碍。
8、定时器
使用定时器ScheduledExecutorService
java.util.concurrent.Executors是ScheduledExecutorService的工厂类,通过Executors,你可以创建你所需要的ScheduledExecutorService。
JDK 1.5之后有了ScheduledExecutorService,不建议你再使用java.util.Timer,因为它无论功能性能都不如ScheduledExecutorService。
大规模定时器TimerWheel
存在一种算法TimerWheel,适用于大规模的定时器实现。这个算法最早是被设计用来实现BSD 内核中定时器的,后来被广泛移植到诸如ACE 等框架中,堪称BSD 中经典算法之一,能针对定时器的各类常见操作提供接近常数时间的响应,且能根据需要很容易进行扩展。
9、并发三大定律:Amdahl、Gustafson、Sun-Ni
Amdahl 定律
Gene Amdahl 发现在计算机体系架构设计过程中,某个部件的优化对整个架构的优化和改善是有上限的。这个发现后来成为知名的Amdahl 定律。
Gustafson 定律
Gustafson假设随着处理器个数的增加,并行与串行的计算总量也是可以增加的。Gustafson定律认为加速系数几乎跟处理器个数成正比,如果现实情况符合Gustafson定律的假设前提的话,那么软件的性能将可以随着处理个数的增加而增加。
Sun-Ni 定律
充分利用存储空间等计算资源,尽量增大问题规模以产生更好/更精确的解。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。