编写并发代码是比较难,尽管Java语言提供了许多同步和并发支持,但是最终写出没有Bug的Java并发代码还是需要依靠个人的勤奋与专业知识。Java多线程并发最佳实践是一组实践的好点子,有助于你快速开发出优质的并发代码。如果你是新手,需要熟悉一些基本概念,再来阅读本文会更有针对性。
1. 使用本地变量
应该总是使用本地变量,而不是创建一个类或实例变量,通常情况下,开发人员使用对象实例作为变量可以节省内存并可以重用,因为他们认为每次在方法中创建本地变量会消耗很多内存。下面代码的execute()方法被多线程调用,为了实现一个新功能,你需要一个临时集合Collection,代码中这个临时集合作为静态类变量使用,然后在execute方法的尾部清除这个集合以便下次重用,编写这段代码的人可能认为这是线程安全的,因为 CopyOnWriteArrayList是线程安全的,但是他没有意识到,这个方法execute()是被多线程调用,那么可能多线程中一个线程看到另外一个线程的临时数据,即使使用Collections.synchronizedList也不能保证execute()方法内的逻辑不变性,这个不变性是:这个集合是临时集合,只用来在每个线程执行内部可见即可,不能暴露给其他线程知晓。
2.使用不可变类
不可变类比如String Integer等一旦创建,不再改变,不可变类可以降低代码中需要的同步数量。
3.最小化锁的作用域范围
任何在锁中的代码将不能被并发执行,如果你有5%代码在锁中,那么根据Amdahl's law,你的应用形象就不可能提高超过20倍,因为锁中这些代码只能顺序执行,降低锁的涵括范围,上锁和解锁之间的代码越少越好。
4.使用线程池的Excutor,而不是直接new Thread执行
创建一个线程的代价是昂贵的,如果你要得到一个可伸缩的Java应用,你需要使用线程池,使用线程池管理线程。JDK提供了各种ThreadPool线程池和Executor。
5.宁可湿衣同步而不要使用线程的wait notify
从Java 1.5以后增加了需要同步工具如CycicBariier, CountDownLatch 和 Sempahore,你应当优先使用这些同步工具,而不是去思考如何使用线程的wait和notify,通过BlockingQueue实现生产-消费的设计比使用线程的wait和notify要好得多,也可以使用CountDownLatch实现多个线程的等待:
import java.util.Date;
import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Java program to demonstrate How to use CountDownLatch in Java. CountDownLatch is
* useful if you want to start main processing thread once its dependency is completed
* as illustrated in this CountDownLatch Example
*
* @author Javin Paul
*/
public class CountDownLatchDemo {
public static void main(String args[]) {
final CountDownLatch latch = new CountDownLatch(3);
Thread cacheService = new Thread(new Service("CacheService", 1000, latch));
Thread alertService = new Thread(new Service("AlertService", 1000, latch));
Thread validationService = new Thread(new Service("ValidationService", 1000, latch));
cacheService.start(); //separate thread will initialize CacheService
alertService.start(); //another thread for AlertService initialization
validationService.start();
// application should not start processing any thread until all service is up
// and ready to do there job.
// Countdown latch is idle choice here, main thread will start with count 3
// and wait until count reaches zero. each thread once up and read will do
// a count down. this will ensure that main thread is not started processing
// until all services is up.
//count is 3 since we have 3 Threads (Services)
try{
latch.await(); //main thread is waiting on CountDownLatch to finish
System.out.println("All services are up, Application is starting now");
}catch(InterruptedException ie){
ie.printStackTrace();
}
}
}
/**
* Service class which will be executed by Thread using CountDownLatch synchronizer.
*/
class Service implements Runnable{
private final String name;
private final int timeToStart;
private final CountDownLatch latch;
public Service(String name, int timeToStart, CountDownLatch latch){
this.name = name;
this.timeToStart = timeToStart;
this.latch = latch;
}
@Override
public void run() {
try {
Thread.sleep(timeToStart);
} catch (InterruptedException ex) {
Logger.getLogger(Service.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println( name + " is Up");
latch.countDown(); //reduce count of CountDownLatch by 1
}
}
Output:
ValidationService is Up
AlertService is Up
CacheService is Up
All services are up, Application is starting now
6.使用BlockingQueue实现生产-消费模式
大部分并发问题都可以使用producer-consumer生产-消费设计实现,而BlockingQueue是最好的实现方式,堵塞的队列不只是可以处理单个生产单个消费,也可以处理多个生产和消费。如下代码:
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ProducerConsumerPattern {
public static void main(String args[]){
//Creating shared object
BlockingQueue sharedQueue = new LinkedBlockingQueue();
//Creating Producer and Consumer Thread
Thread prodThread = new Thread(new Producer(sharedQueue));
Thread consThread = new Thread(new Consumer(sharedQueue));
//Starting producer and Consumer thread
prodThread.start();
consThread.start();
}
}
//Producer Class in java
class Producer implements Runnable {
private final BlockingQueue sharedQueue;
public Producer(BlockingQueue sharedQueue) {
this.sharedQueue = sharedQueue;
}
@Override
public void run() {
for(int i=0; i<10; i++){
try {
System.out.println("Produced: " + i);
sharedQueue.put(i);
} catch (InterruptedException ex) {
Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
//Consumer Class in Java
class Consumer implements Runnable{
private final BlockingQueue sharedQueue;
public Consumer (BlockingQueue sharedQueue) {
this.sharedQueue = sharedQueue;
}
@Override
public void run() {
while(true){
try {
System.out.println("Consumed: "+ sharedQueue.take());
} catch (InterruptedException ex) {
Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
Output:
Produced: 0
Produced: 1
Consumed: 0
Produced: 2
Consumed: 1
Produced: 3
Consumed: 2
Produced: 4
Consumed: 3
Produced: 5
Consumed: 4
Produced: 6
Consumed: 5
Produced: 7
Consumed: 6
Produced: 8
Consumed: 7
Produced: 9
Consumed: 8
Consumed: 9
7. 使用并发集合Collection而不是加了同步锁的集合
Java提供了 ConcurrentHashMap CopyOnWriteArrayList 和 CopyOnWriteArraySet以及BlockingQueue Deque and BlockingDeque五大并发集合,宁可使用这些集合,也不用使用Collections.synchronizedList之类加了同步锁的集合, CopyOnWriteArrayList 适合主要读很少写的场合,ConcurrentHashMap更是经常使用的并发集合
8.使用Semaphore创建有界
为了建立可靠的稳定的系统,对于数据库 文件系统和socket等资源必须有界bound,Semaphore是一个可以限制这些资源开销的选择,如果某个资源不可以,使用Semaphore可以最低代价堵塞线程等待:
import java.util.concurrent.Semaphore;
public class SemaphoreTest {
Semaphore binary = new Semaphore(1);
public static void main(String args[]) {
final SemaphoreTest test = new SemaphoreTest();
new Thread(){
@Override
public void run(){
test.mutualExclusion();
}
}.start();
new Thread(){
@Override
public void run(){
test.mutualExclusion();
}
}.start();
}
private void mutualExclusion() {
try {
binary.acquire();
//mutual exclusive region
System.out.println(Thread.currentThread().getName() + " inside mutual exclusive region");
Thread.sleep(1000);
} catch (InterruptedException i.e.) {
ie.printStackTrace();
} finally {
binary.release();
System.out.println(Thread.currentThread().getName() + " outside of mutual exclusive region");
}
}
}
Output:
Thread-0 inside mutual exclusive region
Thread-0 outside of mutual exclusive region
Thread-1 inside mutual exclusive region
Thread-1 outside of mutual exclusive region
9.宁可使用同步代码块,也不使用加同步的方法
使用synchronized 同步代码块只会锁定一个对象,而不会将当前整个方法锁定;如果更改共同的变量或类的字段,首先选择原子性变量,然后使用volatile。如果你需要互斥锁,可以考虑使用ReentrantLock
10. 避免使用静态变量
静态变量在并发执行环境会制造很多问题,如果你必须使用静态变量,让它称为final 常量,如果用来保存集合Collection,那么考虑使用只读集合。
11.宁可使用锁,而不是synchronized 同步关键字
Lock锁接口是非常强大,粒度比较细,对于读写操作有不同的锁,这样能够容易扩展伸缩,而synchronized不会自动释放锁,如果你使用lock()上锁,你可以使用unlock解锁:
lock.lock();
try {
//do something ...
} finally {
lock.unlock();
}