編寫并發代碼是比較難,盡管Java語言提供了許多同步和并發支持,但是終寫出沒有Bug的Java并發代碼還是需要依靠個人的勤奮與專業知識。Java多線程并發佳實踐是一組實踐的好點子,有助于你快速開發出優質的并發代碼。如果你是新手,需要熟悉一些基本概念,再來閱讀本文會更有針對性。
1. 使用本地變量
應該總是使用本地變量,而不是創建一個類或實例變量,通常情況下,開發人員使用對象實例作為變量可以節省內存并可以重用,因為他們認為每次在方法中創建本地變量會消耗很多內存。下面代碼的execute()方法被多線程調用,為了實現一個新功能,你需要一個臨時集合Collection,代碼中這個臨時集合作為靜態類變量使用,然后在execute方法的尾部清除這個集合以便下次重用,編寫這段代碼的人可能認為這是線程安全的,因為 CopyOnWriteArrayList是線程安全的,但是他沒有意識到,這個方法execute()是被多線程調用,那么可能多線程中一個線程看到另外一個線程的臨時數據,即使使用Collections.synchronizedList也不能保證execute()方法內的邏輯不變性,這個不變性是:這個集合是臨時集合,只用來在每個線程執行內部可見即可,不能暴露給其他線程知曉。
解決辦法是使用本地List而不是全局的List。
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();
}
本站文章版權歸原作者及原出處所有 。內容為作者個人觀點, 并不代表本站贊同其觀點和對其真實性負責,本站只提供參考并不構成任何投資及應用建議。本站是一個個人學習交流的平臺,網站上部分文章為轉載,并不用于任何商業目的,我們已經盡可能的對作者和來源進行了通告,但是能力有限或疏忽,造成漏登,請及時聯系我們,我們將根據著作權人的要求,立即更正或者刪除有關內容。本站擁有對此聲明的最終解釋權。