人人都能掌握的Java服務端性能優化方案

做爲一個Java後端開發,咱們寫出的大部分代碼都決定着用戶的使用體驗。若是咱們的後端代碼性能很差,那麼用戶在訪問咱們的網站時就要浪費一些時間等待服務器的響應。這就可能致使用戶投訴甚至用戶的流失。java

關於性能優化是一個很大的話題。《Java程序性能優化》說性能優化包含五個層次:設計調優、代碼調優、JVM調優、數據庫調優、操做系統調優等。而每個層次又包含不少方法論和最佳實踐。本文不想大而廣的概述這些內容。只是舉幾個經常使用的Java代碼優化方案,讀者看完以後能夠真正的實踐到本身代碼中的方案。算法

使用單例

對於IO處理、數據庫鏈接、配置文件解析加載等一些很是耗費系統資源的操做,咱們必須對這些實例的建立進行限制,或者是始終使用一個公用的實例,以節約系統開銷,這種狀況下就須要用到單例模式。數據庫

單例模式有不少種語法,個人公衆號也推送過多篇和單例相關的文章編程

使用Future模式

假設一個任務執行起來須要花費一些時間,爲了省去沒必要要的等待時間,能夠先獲取一個「提貨單」,即Future,而後繼續處理別的任務,直到「貨物」到達,即任務執行完獲得結果,此時即可以用「提貨單」進行提貨,即經過Future對象獲得返回值。後端

public class RealData implements Callable<String> {  
    protected String data;  

    public RealData(String data) {  
        this.data = data;  
    }  

    @Override  
    public String call() throws Exception {  
        //利用sleep方法來表示真是業務是很是緩慢的  
        try {  
            Thread.sleep(1000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        return data;  
    }  
}  

public class Application {  
    public static void main(String[] args) throws Exception {  
        FutureTask<String> futureTask =   
                new FutureTask<String>(new RealData("name"));  
        ExecutorService executor =   
                Executors.newFixedThreadPool(1); //使用線程池  
        //執行FutureTask,至關於上例中的client.request("name")發送請求  
        executor.submit(futureTask);  
        //這裏能夠用一個sleep代替對其餘業務邏輯的處理  
        //在處理這些業務邏輯過程當中,RealData也正在建立,從而充分了利用等待時間  
        Thread.sleep(2000);  
        //使用真實數據  
        //若是call()沒有執行完成依然會等待  
        System.out.println("數據=" + futureTask.get());  
    }  
}  
複製代碼

使用線程池

合理利用線程池可以帶來三個好處。第一:下降資源消耗。經過重複利用已建立的線程下降線程建立和銷燬形成的消耗。第二:提升響應速度。當任務到達時,任務能夠不須要等到線程建立就能當即執行。第三:提升線程的可管理性。線程是稀缺資源,若是無限制的建立,不只會消耗系統資源,還會下降系統的穩定性,使用線程池能夠進行統一的分配,調優和監控。緩存

在 Java 5 以後,併發編程引入了一堆新的啓動、調度和管理線程的API。Executor 框架即是 Java 5 中引入的,其內部使用了線程池機制,它在 java.util.cocurrent 包下,經過該框架來控制線程的啓動、執行和關閉,能夠簡化併發編程的操做。性能優化

public class MultiThreadTest {
    public static void main(String[] args) {
        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNameFormat("thread-%d").build();
        ExecutorService executor = new ThreadPoolExecutor(2, 5, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), threadFactory);
        executor.execute(new Runnable() {
            @Override
            public void run() {
               System.out.println("hello world !");
            }
        });
        System.out.println(" ===> main Thread! " );
    }
}
複製代碼

使用NIO

JDK自1.4起開始提供全新的I/O編程類庫,簡稱NIO,其不但引入了全新高效的Buffer和Channel,同時,還引入了基於Selector的非阻塞 I/O機制,將多個異步的I/O操做集中到一個或幾個線程當中進行處理,使用NIO代替阻塞I/O能提升程序的併發吞吐能力,下降系統的開銷。服務器

對於每個請求,若是單獨開一個線程進行相應的邏輯處理,當客戶端的數據傳遞並非一直進行,而是斷斷續續的,則相應的線程須要 I/O等待,並進行上下文切換。而使用NIO引入的Selector機制後,能夠提高程序的併發效率,改善這一情況。網絡

public class NioTest {  
    static public void main( String args[] ) throws Exception {  
        FileInputStream fin = new FileInputStream("c:\\test.txt");  
        // 獲取通道  
        FileChannel fc = fin.getChannel();  
        // 建立緩衝區  
        ByteBuffer buffer = ByteBuffer.allocate(1024);  
        // 讀取數據到緩衝區  
        fc.read(buffer);  
        buffer.flip();  
        while (buffer.remaining()>0) {  
            byte b = buffer.get();  
            System.out.print(((char)b));  
        }  
        fin.close();  
    }  
}  
複製代碼

鎖優化

在併發場景中,咱們的代碼中常常會用到鎖。存在鎖,就必然存在鎖的競爭,存在鎖的競爭,就會消耗不少資源。那麼,如何優化咱們Java代碼中的鎖呢?主要能夠從如下幾個方面考慮:多線程

  • 減小鎖持有時間
    • 可使用同步代碼塊來代替同步方法。這樣既能夠減小鎖持有的時間。
  • 減小鎖粒度
    • 要在併發場景中使用Map的時候,記得使用ConcurrentHashMap來代替HashTable和HashMap。
  • 鎖分離
    • 普通鎖(如syncronized)會致使讀阻塞寫、寫也會阻塞讀,同時讀讀與寫寫之間也會進行阻塞,能夠想辦法將讀操做和寫操做分離開。
  • 鎖粗化
    • 有些狀況下咱們但願把不少次鎖的請求合併成一個請求,以下降短期內大量鎖請求、同步、釋放帶來的性能損耗。
  • 鎖消除
    • 鎖消除是Java虛擬機在JIT編譯是,經過對運行上下文的掃描,去除不可能存在共享資源競爭的鎖,經過鎖消除,能夠節省毫無心義的請求鎖時間。

關於鎖優化的內容,後面會出一篇文章詳細介紹。

壓縮傳輸

在進行數據傳輸以前,能夠先將數據進行壓縮,以減小網絡傳輸的字節數,提高數據傳輸的速度,接收端能夠將數據進行解壓,以還原出傳遞的數據,而且,通過壓縮的數據還能夠節約所耗費的存儲介質(磁盤或內存)的空間以及網絡帶寬,下降成本。固然,壓縮也並非沒有開銷的,數據壓縮須要大量的CPU計算,而且,根據壓縮算法的不一樣,計算的複雜度以及數據的壓縮比也存在較大差別。通常狀況下,須要根據不一樣的業務場景,選擇不一樣的壓縮算法。

緩存結果

對於相同的用戶請求,若是每次都重複的查詢數據庫,重複的進行計算,將浪費不少的時間和資源。將計算後的結果緩存到本地內存,或者是經過分佈式緩存來進行結果的緩存,能夠節約寶貴的CPU計算資源,減小重複的數據庫查詢或者是磁盤I/O,將本來磁頭的物理轉動變成內存的電子運動,提升響應速度,而且線程的迅速釋放也使得應用的吞吐能力獲得提高。

參考

Java多線程編程中Future模式的詳解 java鎖優化的方法與思路

相關文章
相關標籤/搜索