作為Java后端開發(fā)者,我們創(chuàng)作的許多代碼直接影響著用戶的使用體驗(yàn)。如果后端代碼性能不佳,用戶在訪問網(wǎng)站時(shí)就必須花費(fèi)更多時(shí)間等待服務(wù)器響應(yīng)。這可能引發(fā)用戶投訴甚至用戶流失問題。
性能優(yōu)化是一個(gè)廣泛而重要的話題?!禞ava程序性能優(yōu)化》提到性能優(yōu)化可分為五個(gè)層次:設(shè)計(jì)優(yōu)化、代碼優(yōu)化、JVM優(yōu)化、數(shù)據(jù)庫優(yōu)化、操作系統(tǒng)優(yōu)化等。每個(gè)層次都涵蓋許多方法論和最佳實(shí)踐。本文無意進(jìn)行全面詳盡的概述,只是列舉幾個(gè)常用的Java代碼優(yōu)化方案,希望讀者閱讀后能實(shí)際應(yīng)用到自己的代碼中。
在處理IO操作、數(shù)據(jù)庫連接、配置文件解析加載等耗費(fèi)大量系統(tǒng)資源的任務(wù)時(shí),我們必須限制這些實(shí)例的創(chuàng)建,或者始終使用一個(gè)共享的實(shí)例,以節(jié)約系統(tǒng)資源。這種情況下就需要使用單例模式。
若有100個(gè)請求,逐個(gè)執(zhí)行顯然效率較低。將這100個(gè)請求合并為一個(gè)請求進(jìn)行批量操作,則能大幅提升效率。
特別是在數(shù)據(jù)庫操作中,批量處理不僅比逐條執(zhí)行效率更高,還能有效降低數(shù)據(jù)庫連接數(shù),提升應(yīng)用的QPS上限。
假設(shè)某項(xiàng)任務(wù)需花費(fèi)一定時(shí)間執(zhí)行,為避免無謂的等待,可先獲取一個(gè)“提貨單”——即Future,隨后繼續(xù)處理其他任務(wù),直至“貨物”抵達(dá),即任務(wù)執(zhí)行完成并獲得結(jié)果。這時(shí)便可憑借“提貨單”提取物品,即通過Future對象獲取返回值。
偽代碼
public class RealData implements Callable<String> { protected String data; public RealData(String data) { this.data = data; } @Override public String call() throws Exception { // 通過sleep方法演示業(yè)務(wù)是緩慢的 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<>(new RealData("name")); ExecutorService executor = Executors.newFixedThreadPool(1); // 使用線程池 // 執(zhí)行FutureTask,相當(dāng)于上例中的client.request("name")發(fā)送請求 executor.submit(futureTask); // 這里可以用一個(gè)sleep代替對其他業(yè)務(wù)邏輯的處理 // 在處理這些業(yè)務(wù)邏輯的同時(shí),RealData也在創(chuàng)建,充分利用等待時(shí)間 Thread.sleep(2000); // 使用真實(shí)數(shù)據(jù) // 如果call()沒有執(zhí)行完成,仍會等待 System.out.println("數(shù)據(jù)=" + futureTask.get()); }}合理運(yùn)用線程池帶來三大益處。首先,降低資源消耗:通過重復(fù)利用已創(chuàng)建的線程,降低線程的創(chuàng)建與銷毀成本。其次,提高響應(yīng)速度:任務(wù)到達(dá)時(shí),無需等待線程創(chuàng)建即可立即執(zhí)行。第三,提升線程可管理性:線程是珍貴資源,無節(jié)制地創(chuàng)建會消耗系統(tǒng)資源,降低系統(tǒng)穩(wěn)定性;線程池能實(shí)現(xiàn)統(tǒng)一分配、優(yōu)化和監(jiān)控。
自 Java 5 開始,引入了并發(fā)編程新API,如Executor框架,內(nèi)部采用線程池機(jī)制,位于java.util.concurrent包中。通過該框架控制線程的啟動(dòng)、執(zhí)行和關(guān)閉,可簡化并發(fā)編程操作。
偽代碼
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! "); }}JDK自1.4版本起引入了新的I/O編程類庫,即NIO。NIO不僅帶來了高效的Buffer和Channel,還引入了基于Selector的非阻塞I/O機(jī)制,可以將多個(gè)異步I/O操作集中到一個(gè)或少數(shù)幾個(gè)線程中進(jìn)行處理。使用NIO替代阻塞I/O能夠提高程序的并發(fā)吞吐能力,降低系統(tǒng)開銷。
針對每個(gè)請求,如果為其單獨(dú)開啟一個(gè)線程來處理邏輯,當(dāng)客戶端數(shù)據(jù)傳輸是間歇性的而非連續(xù)的時(shí),相應(yīng)線程會處于I/O等待狀態(tài),并頻繁進(jìn)行上下文切換。利用NIO引入的Selector機(jī)制,可以提升程序的并發(fā)效率,改善這種狀況。
偽代碼
public class NioTest { static public void main( String args[] ) throws Exception { FileInputStream fin = new FileInputStream("D://test.txt"); // 獲取通道 FileChannel fc = fin.getChannel(); // 創(chuàng)建緩沖區(qū) ByteBuffer buffer = ByteBuffer.allocate(1024); // 讀取數(shù)據(jù)到緩沖區(qū) fc.read(buffer); buffer.flip(); while (buffer.remaining()>0) { byte b = buffer.get(); System.out.print(((char)b)); } fin.close(); } }在并發(fā)場景中,頻繁使用鎖是很常見的情況。然而,鎖引發(fā)競爭,而競爭又會耗費(fèi)大量資源。那么,在Java代碼中,如何優(yōu)化鎖呢?我們可以考慮以下幾個(gè)方面:
在并發(fā)環(huán)境中使用Map時(shí),最好選用ConcurrentHashMap替代HashTable和HashMap(ConcurrentHashMap采用分段鎖,鎖的粒度更細(xì))。
分離鎖
普通鎖(例如synchronized)可能導(dǎo)致讀寫互相阻塞,可以嘗試將讀操作和寫操作分開。
鎖粗化
有時(shí)我們希望將多次鎖的請求合并成一個(gè),以減少頻繁加鎖、同步和解鎖所帶來的性能損失。
鎖消除
鎖消除是指Java虛擬機(jī)在JIT編譯時(shí),經(jīng)過運(yùn)行上下文的掃描,去除那些不會產(chǎn)生共享資源競爭的鎖。通過鎖消除,可以減少無謂的鎖請求時(shí)間。
在數(shù)據(jù)傳輸之前,壓縮數(shù)據(jù)是一種優(yōu)化方式,可以減少網(wǎng)絡(luò)傳輸?shù)臄?shù)據(jù)量,提升傳輸速度。接收端可解壓數(shù)據(jù),還原傳輸內(nèi)容。壓縮后的數(shù)據(jù)能節(jié)省存儲介質(zhì)(如磁盤或內(nèi)存)空間和網(wǎng)絡(luò)帶寬,從而降低成本。然而,壓縮并非無成本之舉。數(shù)據(jù)壓縮需要大量CPU計(jì)算,并且根據(jù)壓縮算法的不同,計(jì)算復(fù)雜度和壓縮比都有顯著差異。通常需要根據(jù)業(yè)務(wù)情景選擇合適的壓縮算法。
對于相同的用戶請求,若每次都重復(fù)查詢數(shù)據(jù)庫、重復(fù)計(jì)算,將浪費(fèi)大量時(shí)間和資源。將計(jì)算結(jié)果緩存至本地內(nèi)存或使用分布式緩存,可節(jié)約寶貴的CPU計(jì)算資源,減少數(shù)據(jù)庫重復(fù)查詢或磁盤I/O。將原本需要磁頭物理轉(zhuǎn)動(dòng)的操作轉(zhuǎn)化為內(nèi)存中的電子運(yùn)動(dòng),提高響應(yīng)速度。同時(shí),快速釋放線程也提升了應(yīng)用的吞吐能力。
本文鏈接:http://m.www897cc.com/showinfo-26-81731-0.html一篇文章告訴你真實(shí)場景下服務(wù)端接口性能問題是如何解決的
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時(shí)與本網(wǎng)聯(lián)系,我們將在第一時(shí)間刪除處理。郵件:2376512515@qq.com
上一篇: 京東一面:如何在SpringBoot啟動(dòng)時(shí)執(zhí)行特定代碼?
下一篇: 詳解WebSocketMessageBrokerConfigurer在SpringBoot中應(yīng)用與實(shí)踐