精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

API性能提升寶典:12個必殺技

開發 前端
消息隊列天然就有簡化系統復雜性的作用,它通過異步的方式將任務與任務之間的關系進行解耦,也就達到了減少服務之間依賴的效果。

1. 并行處理

簡要說明

舉個例子:在價格查詢鏈路中,我們需要獲取多種獨立的價格配置項信息,如基礎價、折扣價、商戶活動價、平臺活動價等等。為了加快處理速度,可以使用多線程并行處理的方式,利用并發計算的優勢。而 CompletableFuture 是一種流行的實現多線程的方式,它可以輕松地管理線程的創建、執行和回調,提高程序的可擴展性和并發性。

然而,多線程的使用也存在一些弊端,例如硬件資源的限制和線程間的通信開銷等。因此,我們需要在使用多線程的同時,考慮到 I/O 密集型和 CPU 密集型的差異,以避免過度開啟線程導致性能下降。同時,對于線程池的運行情況,我們也需要有一定的了解和控制,以確保程序的高效穩定運行。

CompletableFuture 是銀彈嗎?

我們常說“手拿錘子看什么都像釘子”,使用 CompletableFuture 的確能夠幫助我們解決許多獨立處理邏輯的問題,但是如果使用過多的線程,反而會導致線程調度時間不能得到保障,線程會被浪費在等待 CPU 時間片上,特別是對于那些本來執行速度就很快的任務,使用 CompletableFuture 之后反而會拖慢整體執行時長。

因此,在使用 CompletableFuture 時,我們需要根據具體的場景和任務,仔細考慮是否需要并行處理。如果需要并行處理,我們需要根據任務的性質和執行速度,選擇合適的線程池大小和并行線程數量,以避免線程調度時間的浪費和執行效率的下降。

測試案例

執行 a,b,c,d4 個方法,比較同步執行與異步執行的耗時情況。

全同步執行
private void test() {
    long s = System.currentTimeMillis();
    a(10);
    b(10);
    c(10);
    d(10);
    long e = System.currentTimeMillis();
    System.out.println(e - s);
}

public void a(int time) {
    try {
        Thread.sleep(time);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

public void b(int time) {
    try {
        Thread.sleep(time);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

public void c(int time) {
    try {
        Thread.sleep(time);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

public void d(int time) {
    try {
        Thread.sleep(time);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
全異步執行
private void test2() {
    long s = System.currentTimeMillis();
    List<CompletableFuture<?>> completableFutureList = new ArrayList <>();
    CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
        a(10);
    });
    completableFutureList.add(future1);
    CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
        b(10);
    });
    completableFutureList.add(future2);
    CompletableFuture<Void> future3 = CompletableFuture.runAsync(() -> {
        c(10);
    });
    completableFutureList.add(future3);
    CompletableFuture<Void> future4 = CompletableFuture.runAsync(() -> {
        d(10);
    });
    completableFutureList.add(future4);
    CompletableFuture<?>[] futures = completableFutureList.toArray(newCompletableFuture[0]);
    CompletableFuture<Void> futureAll = CompletableFuture.allOf(futures);
    futureAll.join();
    long e = System.currentTimeMillis();
    System.out.println(e - s);
}
結果統計

圖片圖片

測試結論

在分配了相對合理的線程池的情況下,通過以上分析,可以得出下列兩個結論:

? 方法耗時越少,同步比異步越好。

? 方法數量越少,同步比異步越好。

半異步,半同步

有時候,如果方法較多,為了減少高并發時 P99 較高,我們可以讓耗時多的方法異步執行,耗時少的方法同步執行。

通過以下數據可以看出,耗時是差不多的,但可以節省不少線程資源。

圖片圖片

總結

CompletableFuture 提供了一種優雅而強大的方式來處理并發請求和任務。然而,正如在處理高并發時使用過多的線程會導致資源浪費和效率下降一樣,使用過多的 CompletableFuture 也會導致同樣的問題。這種現象被稱為 "線程調度問題",它會導致性能下降和吞吐量下降(P99 值較高)。

因此,我們需要在使用 CompletableFuture 時考慮實際場景和負載情況,并根據需要使用恰當的技術來優化性能。

2. 最小化事務范圍

簡要說明

首先,我們需要明確的是,事務的存在勢必會對性能產生影響,特別是在高并發的情況下,因為鎖的競爭,會帶來極大的性能損耗。因此,在處理數據交互的過程中,我們始終堅持盡可能地減少事務的范圍,從而提升接口的響應速度。

一般來說,我們可以利用@Transactional 注解輕松實現事務的控制。但是,由于@Transactional 注解的最小粒度僅限于方法級別,因此,為了更好地控制事務的范圍,我們需要通過編程式事務來實現。

在編程式事務中,我們可以更靈活地控制事務的開啟和結束,以及對數據庫操作的處理。通過適當的設置事務參數和操作規則,我們可以實現事務的最小化,從而提升系統的性能和可靠性。

編程式事務模板

public interface TransactionControlService {
    /**
     * 事務處理
     *
     * @param objectLogicFunction 業務邏輯
     * @param <T>                 result type
     * @return 處理結果
     * @throws Exception 業務異常信息
     */
    <T> T execute(ObjectLogicFunction<T> objectLogicFunction) throws Exception;
    /**
     * 事務處理
     *
     * @param voidLogicFunction 業務邏輯
     * @throws Exception 業務異常信息
     */
    void execute(VoidLogicFunction voidLogicFunction) throws Exception;
}
@Service
public class TransactionControlServiceImpl implements TransactionControlService {

    @Autowired
    private PlatformTransactionManager platformTransactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;

    /**
     * 事務處理
     *
     * @param businessLogic 業務邏輯
     * @param <T>           result type
     * @return 處理結果
     * @throws Exception 業務異常信息
     */
    @Override
    public <T> T execute(ObjectLogicFunction<T> businessLogic) throws Exception {
        TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
        try {
            T resp = businessLogic.logic();
            platformTransactionManager.commit(transactionStatus);
            return resp;
        } catch (Exception e) {
            platformTransactionManager.rollback(transactionStatus);
            throw new Exception(e);
        }
    }

    /**
     * 事務處理
     *
     * @param businessLogic 業務邏輯
     */
    @Override
    public void execute(VoidLogicFunction businessLogic) throws Exception {
        TransactionStatus transactionStatus = platformTransactionManager.getTransaction(transactionDefinition);
        try {
            businessLogic.logic();
            platformTransactionManager.commit(transactionStatus);
        } catch (Exception e) {
            platformTransactionManager.rollback(transactionStatus);
            throw new Exception(e);
        }
    }
}

@FunctionalInterface
public interface ObjectLogicFunction<T> {

    /**
     * 業務邏輯處理
     *
     * @return 業務處理結果
     * @throws BusinessException e
     */
    T logic() throws BusinessException;
}

@FunctionalInterface
public interface VoidLogicFunction {

    /**
     * 業務邏輯處理
     *
     * @throws Exception e
     */
    void logic() throws Exception;
}

transactionControlService.execute(() -> {
    // 把需要事務控制的業務邏輯寫在這里即可
});

3. 緩存

簡要說明

緩存,這一在性能提升方面堪稱萬金油的技術手段,它的重要性在各種計算機應用領域中無可比擬。

緩存作為一種高效的數據讀取和寫入的優化方式,被廣泛應用于各種領域,包括電商、金融、游戲、直播等。

雖然在網絡上關于緩存的文章不勝枚舉,但要想充分發揮緩存的作用,需要針對具體的業務場景進行深入分析和探討。因此,在本節中,我們將不過多贅述緩存的具體使用方法,而是重點列舉一些使用緩存時的注意事項.

使用緩存時的注意事項

? 緩存過期時間: 設置合適的過期時間可以保證緩存的有效性,但過期時間過長可能會浪費內存空間,過期時間過短可能會導致頻繁刷新緩存,影響性能。

? 緩存一致性: 如果緩存的數據與數據庫中的數據不一致,可能會導致業務邏輯出現問題。因此,在使用緩存時需要考慮緩存一致性的問題。

? 緩存容量限制: 緩存容量有限,如果緩存的數據量過大,可能會導致內存溢出或者緩存頻繁清理。因此,在使用緩存時需要注意緩存容量的限制。

? 緩存需要考慮負載均衡: 在高并發場景下,需要考慮緩存的負載均衡問題,避免某些緩存服務器因為熱點數據等問題負載過重導致系統崩潰或者響應變慢。

? 緩存需要考慮并發讀寫: 當多個用戶同時訪問緩存時,需要考慮并發讀寫的問題,避免緩存沖突和數據一致性問題。

? 緩存穿透問題: 當大量的查詢請求都無法命中緩存時,導致每次查詢都會落到數據庫上,從而造成數據庫壓力過大。

? 緩存擊穿問題: 當緩存數據失效后,導致大量的請求直接打到數據庫中,從而造成數據庫壓力過大。

? 查詢時間復雜度: 需額外注意緩存查詢的時間復雜度問題,如果是 O(n),甚至更差的時間復雜度,則會因為緩存的數據量增加而跟著增加。

考慮到這些問題通常優化的手段

? 數據壓縮: 選擇合理的數據類型,舉個例子:如果用 Integer[]  和 int[]來比較,Integer 占用的空間大約是 int 的 4 倍。其他情況下,使用一些常見數據編碼壓縮技術也是常見的節省內存的方式,比如:BitMap、字典編碼等。

? 預加載: 當行為可預測時,那么提前加載便可解決構建緩存時的壓力。

? 熱點數據: 熱點數據如果不能打散,那么通常就會構建多級緩存,比如將應用服務設為一級緩存,Redis 設為二級緩存,一級緩存,緩存全量熱點數據,從而實現壓力分攤。

? 緩存穿透、擊穿: 針對命中不了緩存的查詢也可以緩存一個額外的標識;而針對緩存失效,要么就在失效前,主動刷新一次,要么就分散失效時間,避免大量緩存同時失效。

  • ? 時間復雜度: 在設計緩存時,優先考慮選擇常數級的時間復雜度的方法。

4. 合理使用線程池

簡要說明

在本文開始提到的使用 CompletableFuture 并行處理時,實際上就已經使用到線程池了,池化技術的好處,我想應該不用再過多闡述了,但關于線程池的使用還是有很多注意點的。

使用場景

異步任務

簡單來說就是某些不需要同步返回業務處理結果的場景,比如:短信、郵件等通知類業務,評論、點贊等互動性業務。

并行計算

就像 MapReduce 一樣,充分利用多線程的并行計算能力,將大任務拆分為多個子任務,最后再將所有子任務計算后的結果進行匯總,ForkJoinPool 就是 JDK 中典型的并行計算框架。

同步任務

前面講到的 CompletableFuture 使用,就是典型的同步改異步的方式,如果任務之間沒有依賴,那么就可以利用線程,同時進行處理,這樣理論上就只需要等待耗時最長的步驟結束即可(實際情況可參考 CompletableFuture 分析)。

線程池的創建

不要直接使用 Executors 創建線程池,應通過 ThreadPoolExecutor 的方式,主動明確線程池的參數,避免產生意外。

每個參數都要顯示設置,例如像下面這樣:

private static final ExecutorService executor = new ThreadPoolExecutor(
        2,
        4,
        1L,
        TimeUnit.MINUTES,
        new LinkedBlockingQueue<>(100),
        new ThreadFactoryBuilder().setNameFormat("common-pool-%d").build(),
        new ThreadPoolExecutor.CallerRunsPolicy());

參數的配置建議

CorePoolSize(核心線程數)

一般在配置核心線程數的時候,是需要結合線程池將要處理任務的特性來決定的,而任務的性質一般可以劃分為:CPU 密集型、I/O 密集型。

比較通用的配置方式如下

? CPU 密集型: 一般建議線程的核心數與 CPU 核心數保持一致。

? I/O 密集型: 一般可以設置 2 倍的 CPU 核心數的線程數,因為此類任務 CPU 比較空閑,可以多分配點線程充分利用 CPU 資源來提高效率。

通過Runtime.getRuntime().availableProcessors()可以獲取核心線程數。

另外還有一個公式可以借鑒

? 線程核心數 = cpu 核心數 / (1-阻塞系數)

? 阻塞系數 = 阻塞時間/(阻塞時間+使用 CPU 的時間)

實際上大多數線上業務所消耗的時間主要就是 I/O 等待,因此一般線程數都可以設置的多一點,比如 tomcat 中默認的線程數就是 200,所以最佳的核心線程數是需要根據特定場景,然后通過實際上線上允許結果分析后,再不斷的進行調整。

MaximumPoolSize

maximumPoolSize 的設置也是看實際應用場景,如果設置的和 corePoolSize 一樣,那就完全依靠阻塞隊列和拒絕策略來控制任務的處理情況,如果設置的比 corePoolSize 稍微大一點,那就可以更好的應對一些有突發流量產生的場景。

KeepAliveTime

由 maximumPoolSize 創建出來的線程,在經過 keepAliveTime 時間后進行銷毀,依據突發流量持續的時間來決定。

WorkQueue

那么阻塞隊列應該設置多大呢?我們知道當線程池中所有的線程都在工作時,如果再有任務進來,就會被放到阻塞隊列中等待,如果阻塞隊列設置的太小,可能很快隊列就滿了,導致任務被丟棄或者異常(由拒絕策略決定),如果隊列設置的太大,又可能會帶來內存資源的緊張,甚至 OOM,以及任務延遲時間過長。

所以阻塞隊列的大小,又是要結合實際場景來設置的。

一般會根據處理任務的速度與任務產生的速度進行計算得到一個大概的數值。

假設現在有 1 個線程,每秒鐘可以處理 10 個任務,正常情況下每秒鐘產生的任務數小于 10,那么此時隊列長度為 10 就足以。

但是如果高峰時期,每秒產生的任務數會達到 20,會持續 10 秒,且任務又不希望丟棄,那么此時隊列的長度就需要設置到 100。

監控 workQueue 中等待任務的數量是非常重要的,只有了解實際的情況,才能做出正確的決定。

在有些場景中,可能并不希望因為任務被丟進阻塞隊列而等待太長的時間,而是希望直接開啟設置的 MaximumPoolSize 線程池數來執行任務,這種情況下一般可以直接使用 SynchronousQueue 隊列來實現

ThreadFactory

通過 threadFactory 我們可以自定義線程組的名字,設置合理的名稱將有利于你線上進行問題排查。

Handler

最后拒絕策略,這也是要結合實際的業務場景來決定采用什么樣的拒絕方式,例如像過程類的數據,可以直接采用 DiscardOldestPolicy 策略。

線程池的監控

線上使用線程池時,一定要做好監控,以便根據實際運行情況進行調整,常見的監控方式可以通過線程池提供的 API,然后暴露給 Metrics 來完成實時數據統計。

監控示例

線程池自身提供的統計數據

public class ThreadPoolMonitor {

    private final static Logger log = LoggerFactory.getLogger(ThreadPoolMonitor.class);

    private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 0,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>(100),
            new ThreadFactoryBuilder().setNameFormat("my_thread_pool_%d").build());

    public static void main(String[] args) {
        log.info("Pool Size: " + threadPool.getPoolSize());
        log.info("Active Thread Count: " + threadPool.getActiveCount());
        log.info("Task Queue Size: " + threadPool.getQueue().size());
        log.info("Completed Task Count: " + threadPool.getCompletedTaskCount());
    }
}

通過 micrometer API 完成統計,這樣就可以接入Prometheus了

@Component
public class ThreadPoolMonitor {

    private static final ThreadPoolExecutor threadPool = new ThreadPoolExecutor(4, 8, 0,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>(100),
            new ThreadFactoryBuilder().setNameFormat("my_thread_pool_%d").build(), new ThreadPoolExecutor.DiscardOldestPolicy());

    /**
     * 活躍線程數
     */
    private AtomicLong activeThreadCount = new AtomicLong(0);

    /**
     * 隊列任務數
     */
    private AtomicLong taskQueueSize = new AtomicLong(0);

    /**
     * 完成任務數
     */
    private AtomicLong completedTaskCount = new AtomicLong(0);

    /**
     * 線程池中當前線程的數量
     */
    private AtomicLong poolSize = new AtomicLong(0);

    @PostConstruct
    private void init() {

        /**
         * 通過micrometer API完成統計
         *
         * gauge最典型的使用場景就是統計:list、Map、線程池、連接池等集合類型的數據
         */
        Metrics.gauge("my_thread_pool_active_thread_count", activeThreadCount);
        Metrics.gauge("my_thread_pool_task_queue_size", taskQueueSize);
        Metrics.gauge("my_thread_pool_completed_task_count", completedTaskCount);
        Metrics.gauge("my_thread_pool_size", poolSize);

        // 模擬線程池的使用
        new Thread(this::runTask).start();
    }

    private void runTask() {
        // 每5秒監控一次線程池的使用情況
        monitorThreadPoolState();
        // 模擬任務執行
        IntStream.rangeClosed(0, 500).forEach(i -> {
            // 每500毫秒,執行一個任務
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 每個處理一個任務耗時5秒
            threadPool.submit(() -> {
                try {
                    TimeUnit.MILLISECONDS.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        });
    }

    private void monitorThreadPoolState() {
        Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(() -> {
            activeThreadCount.set(threadPool.getActiveCount());
            taskQueueSize.set(threadPool.getQueue().size());
            poolSize.set(threadPool.getPoolSize());
            completedTaskCount.set(threadPool.getCompletedTaskCount());
        }, 0, 5, TimeUnit.SECONDS);
    }
}

線程池的資源隔離

在生產環境中,一定要注意好資源隔離的問題,盡量不要將不同類型,不同重要等級的任務放入一個線程池中,以免因為線程資源爭搶而互相影響。

5. 服務預熱

服務預熱也是很常見的一種優化手段,例如數據庫連接、線程池中的核心線程,緩存等信息可以利用服務啟動階段預先加載,從而避免請求到來后臨時構建的耗時。

下面提供一些預加載的方式

線程池

線程池本身提供了相關的 API:prestartAllCoreThreads()通過該方法可以提前將核心線程創建好,非常方便。

Web 服務

常見的如 Tomcat,其本身也用到了線程池,只是其自身已經考慮到了預加載的問題,不需要我們額外處理了。

圖片圖片

連接池

連接池常用的一般就是數據庫連接池以及Redis連接池,大多數這些連接的客戶端也都做了連接提前加載的工作,遇到沒有預加載的參考其他客戶端方式搞一下即可。

緩存

一般本地緩存可以在每次服務啟動時預先加載好,以免出現緩存擊穿的情況。

靜態代碼塊

在服務啟動時,靜態代碼塊中的相關功能會優先被加載,可以有效避免在運行時再加載的情況。

其他擴展

預熱實際上可聊的內容很多,一般有用到池化技術的方式,都是需要預熱的,為了能夠提升響應性能,將不在內存中的數據提前查好放入內存中,或者將需要計算的數據提前計算好,這都是很容易想到的解決方式。

此外還有一些服務端在設計之初就會針對性地對一些熱點數據進行特殊處理,比如JVM中的JIT、內存分配比;OS中的page cache;MySQL中的innodb_buffer_pool等,這些一般可以通過流量預熱的方式來使其達到最佳狀態。

6. 緩存對齊

CPU的多級緩存

CPU緩存通常分為大小不等的三級緩存

來自百度百科對三級緩存分類的介紹:

一級緩存都內置在CPU內部并與CPU同速運行,可以有效的提高CPU的運行效率。一級緩存越大,CPU的運行效率越高,但受到CPU內部結構的限制,一級緩存的容量都很小。

二級緩存,它是為了協調一級緩存和內存之間的速度。cpu調用緩存首先是一級緩存,當處理器的速度逐漸提升,會導致一級緩存就供不應求,這樣就得提升到二級緩存了。二級緩存它比一級緩存的速度相對來說會慢,但是它比一級緩存的空間容量要大。主要就是做一級緩存和內存之間數據臨時交換的地方用。

三級緩存是為讀取二級緩存后未命中的數據設計的—種緩存,在擁有三級緩存的CPU中,只有約5%的數據需要從內存中調用,這進一步提高了CPU的效率。其運作原理在于使用較快速的儲存裝置保留一份從慢速儲存裝置中所讀取數據并進行拷貝,當有需要再從較慢的儲存體中讀寫數據時,緩存(cache)能夠使得讀寫的動作先在快速的裝置上完成,如此會使系統的響應較為快速。

效果演示

逐行寫入
public class CacheLine {
    public static void main(String[] args) {
        int[][] arr = new int[10000][10000];
        long s = System.currentTimeMillis();
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr[i].length; j++) {
                arr[i][j] = 0;
            }
        }
        long e = System.currentTimeMillis();
        System.out.println(e-s);
    }
}
逐列寫入
public class CacheLine {
    public static void main(String[] args) {
        int[][] arr = new int[10000][10000];
        long s = System.currentTimeMillis();
        for (int i = 0; i < arr.length; i++) {
            for (int j = 0; j < arr[i].length; j++) {
                arr[j][i] = 0;
            }
        }
        long e = System.currentTimeMillis();
        System.out.println(e-s);
    }
}

雖然兩種方式得到的結果是一樣的,但性能對比卻相差巨大,這就是緩存行帶來的影響。

原因分析

CPU的緩存是由多個緩存行組成的,以緩存行為基本單位,一個緩存行的大小一般為64字節,二維數組在內存中保存時,實際上是以按行遍歷的方式進行保存,比如:arr[0][0],arr[0][1],arr[1][0],arr[1][1],arr[2][0],arr[2][1]...

所以當按行訪問時,是按照內存存儲的順序進行訪問,那么CPU緩存后面的元素就可以利用到,而如果是按列訪問,那么CPU的緩存是沒有用的。

緩存行對齊

public class CacheLinePadding {
    private static class Padding {
     // 一個long是8個字節,一共7個long
        // public volatile long p1, p2, p3, p4, p5, p6, p7;
    }

    private static class T extends Padding {
     // x變量8個字節,加上Padding中的變量,剛好64個字節,獨占一個緩存行。
        public volatile long x = 0L;
    }

    public static T[] arr = new T[2];

    static {
        arr[0] = new T();
        arr[1] = new T();
    }

    public static void main(String[] args) throws Exception {
        Thread t1 = new Thread(() -> {
            for (long i = 0; i < 10000000; i++) {
                arr[0].x = i;
            }
        });

        Thread t2 = new Thread(() -> {
            for (long i = 0; i < 10000000; i++) {
                arr[1].x = i;
            }
        });

        final long start = System.nanoTime();
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println((System.nanoTime() - start)  / 100000);
    }
}

同樣的含有public volatile long p1, p2, p3, p4, p5, p6, p7;這一行代碼與不含性能也相差巨大,這同樣也是因為緩存行的原因,當運行在兩個不同CPU上的兩個線程要寫入。

7. 減少對象的產生

避免使用包裝類型

因為包裝類型的創建和銷毀都會產生臨時對象,因此相比基本數據類型來說,會帶來額外的消耗。

public class Main {

    public static void main(String[] args) {
        long s = System.currentTimeMillis();
        testInteger();
        long e = System.currentTimeMillis();
        System.out.println(e - s);
        testInt();
        long e2 = System.currentTimeMillis();
        System.out.println(e2 - e);
    }

    private static void testInt() {
        int sum = 1;
        for (int i = 1; i < 50000000; i++) {
            sum++;
        }
        System.out.println(sum);
    }

    private static void testInteger() {
        Integer sum = 1;
        for (int i = 1; i < 50000000; i++) {
            sum++;
        }
        System.out.println(sum);
    }
}

兩個方法不僅執行時間相差百倍,在CPU和內存的消耗上Integer也明顯弱于int。

Integer內存和CPU都能看到明顯的波動

int幾乎沒波動int幾乎沒波動

圖片圖片

使用不可變對象

最為典型的案例就是String,我想應該不會有人去通過new的方式再去構建一個String字符串了吧!

String str = new String("abc"); 
String str = "abc";

同時,在實現字符串連接時通常使用StringBuilder或StringBuffer,這樣可以避免使用連接符,導致每次都創建新的字符串對象。

靜態方法

靜態對象

Boolean.valueOf("true");

public static Boolean valueOf(String s) {
    return parseBoolean(s) ? TRUE : FALSE;
}

public static final Boolean TRUE = new Boolean(true);

public static final Boolean FALSE = new Boolean(false);

靜態工廠(單例模式)

public class StaticSingleton {
    private static class StaticHolder {
        public static final StaticSingleton INSTANCE = new StaticSingleton();
    }

    public static StaticSingleton getInstance() {
        return StaticHolder.INSTANCE;
    }
}

枚舉

public enum EnumSingleton { INSTANCE; }

視圖

視圖是返回引用的一種方式。

map的keySet方法,實際上每次返回的都是同一個對象的引用。

public Set<K> keySet() {
    Set<K> ks = keySet;
    if (ks == null) {
        ks = new KeySet();
        keySet = ks;
    }
    return ks;
}

對象池

對象池可以有效減少頻繁的對象創建和銷毀的過程,一般情況下如果每次創建對象的過程較為復雜,且對象占用空間又比較大,那么就建議使用對象池的方式來優化。

使用示例

org.apache.commons提供了對象池的工具類,可以直接拿來使用

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.11.1</version>
</dependency>

池化的對象

@Data
public class Cache {
    private byte[] size;
}

池化對象工廠

public class CachePoolObjectFactory extends BasePooledObjectFactory<Cache> {

    @Override
    public Cache create() {
        Cache cache = new Cache();
        cache.setSize(new byte[1024 * 1024 * 16]);
        return cache;
    }

    @Override
    public PooledObject<Cache> wrap(Cache cache) {
        return new DefaultPooledObject<>(cache);
    }
}

對象池工具

public enum CachePoolUtil {

    INSTANCE;

    private GenericObjectPool<Cache> objectPool;

    CachePoolUtil() {
        GenericObjectPoolConfig<Cache> poolConfig = new GenericObjectPoolConfig<>();
        // 對象池中最大對象數
        poolConfig.setMaxTotal(50);
        // 對象池中最小空閑對象數
        poolConfig.setMinIdle(20);
        // 對象池中最大空閑對象數
        poolConfig.setMaxIdle(20);
        // 獲取對象最大等待時間 默認 -1 一直等待
        poolConfig.setMaxWait(Duration.ofSeconds(3));
        // 創建對象工廠
        CachePoolObjectFactory objectFactory = new CachePoolObjectFactory();
        // 創建對象池
        objectPool = new GenericObjectPool<>(objectFactory, poolConfig);
    }

    /**
     * 從對象池中取出一個對象
     */
    public Cache borrowObject() throws Exception {
        return objectPool.borrowObject();
    }

    public void returnObject(Cache cache) {
        // 將對象歸還給對象池
        objectPool.returnObject(cache);
    }

    /**
     * 獲取活躍的對象數
     */
    public int getNumActive() {
        return objectPool.getNumActive();
    }

    /**
     * 獲取空閑的對象數
     */
    public int getNumIdle() {
        return objectPool.getNumIdle();
    }

}
public class Main {

    public static void main(String[] args) {
        CachePoolUtil cachePoolUtil = CachePoolUtil.INSTANCE;
        for (int i = 0; i < 10; i++) {
            new Thread(new Runnable() {
                @SneakyThrows
                @Override
                public void run() {
                    while (true) {
                        Thread.sleep(100);
                        
                        // 使用對象池
                        Cache cache = cachePoolUtil.borrowObject();
                        m(cache);
                        cachePoolUtil.returnObject(cache);
                        
                        // 不使用對象池
                        //Cache cache = new Cache();
                        //cache.setSize(new byte[1024 * 1024 * 2]);
                        //m(cache);

                    }

                }
            }).start();
        }
    }
    
    // 無特殊作用
    public static void m(Cache cache) {
        if (cache.getSize().length < 10) {
            System.out.println(cache);
        }
    }
}

使用對象池

圖片圖片

不適用對象池

圖片圖片

8. 并發處理

鎖的粒度控制

并發場景下就要考慮線程安全的問題,常見的解決方式:volatile、CAS、自旋鎖、對象鎖、類鎖、分段鎖、讀寫鎖,理論上來說,鎖的粒度越小,并行效果就越高。

volatile

volatile是Java中的一個關鍵字,用于修飾變量。它的作用是保證被volatile修飾的變量在多線程環境下的可見性和禁止指令重排序。

volatile雖然不能保證原子性,但如果對共享變量是純賦值或讀取的操作,那么因為volatile保證了可見性,因此也是可以實現線程安全的。

CAS

compare and swap(比較并交換),CAS主要有三個參數,

? V:內存值

? A:當前時

? B:待更新的值

當且僅當V等于A時,就將A更新為B,否則什么都不做。V和A的比較是一個原子性操作保證線程安全。

Random通過cas的方式保證了線程安全,但在高并發下很有可能會失敗,造成頻繁的重試。

protected int next(int bits) {
    long oldseed, nextseed;
    AtomicLong seed = this.seed;
    do {
        oldseed = seed.get();
        nextseed = (oldseed * multiplier + addend) & mask;
    } while (!seed.compareAndSet(oldseed, nextseed));
    return (int)(nextseed >>> (48 - bits));
}

ThreadLocalRandom進行了優化,其主要方式就是分段,通過讓每個線程擁有獨立的存儲空間,這樣即保證了線程安全,同時效率也不會太差。

public static ThreadLocalRandom current() {
    if (U.getInt(Thread.currentThread(), PROBE) == 0)
        localInit();
    return instance;
}
static final void localInit() {
    int p = probeGenerator.addAndGet(PROBE_INCREMENT);
    int probe = (p == 0) ? 1 : p; // skip 0
    long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
    Thread t = Thread.currentThread();
    U.putLong(t, SEED, seed);
    U.putInt(t, PROBE, probe);
}
public int nextInt() {
    return mix32(nextSeed());
}
final long nextSeed() {
    Thread t; long r; // read and update per-thread seed
    U.putLong(t = Thread.currentThread(), SEED,
              r = U.getLong(t, SEED) + GAMMA);
    return r;
}
對象鎖、類鎖

主要就是通過synchronized實現,是最基礎的鎖機制。

自旋鎖

在自旋鎖中,當一個操作需要訪問一個共享資源時,它會檢查這個資源是否被其他操作占用。如果是,它會一直等待,直到資源被釋放。在等待期間,這個操作會進入一個自旋狀態,也就是不會被系統掛起,但是也不會繼續執行其他任務。當資源被釋放后,這個操作會立即返回并繼續執行下一步操作。

自旋鎖是一種簡單而有效的同步機制,自旋鎖的優點是減少線程上下文切換的開銷,但是它也有一些缺點。由于它需要一直進行自旋操作,所以會消耗一定的CPU資源。因此,在使用自旋鎖時需要仔細考慮并發問題和性能問題。

分段鎖

在分段鎖的模型中,共享數據被分割成若干個段,每個段都被一個鎖所保護,同時只有一個線程可以在同一時刻對同一段進行加鎖和解鎖操作。這種鎖機制可以降低鎖的競爭,提高并發訪問的效率。

ConcurrentHashMap的設計就是采用分段鎖的思想,其會按照map中的table capacity(默認16)來劃分,也就是說每個線程會鎖1/16的數據段,這樣一來就大大提升了并發訪問的效率。

讀寫鎖

讀寫鎖主要根據大多數業務場景都是讀多寫少的情況,在讀數據時,無論多少線程同時訪問都不會有安全問題,所以在讀數據的時候可以不加鎖,不過一旦有寫請求時就需要加鎖了。

? 讀、讀:不沖突

? 讀、寫:沖突

? 寫、寫:沖突

典型的如:ReentrantReadWriteLock

圖片圖片

寫時復制

寫時復制最大的優勢在于,在寫數據的過程時,不影響讀,可以理解為讀的是數據的副本,而只有當數據真正寫完后才會替換副本,當副本特別大、寫數據過程比較漫長時,寫時復制就特別有用了。

CopyOnWriteArrayList、CopyOnWriteArraySet就是集合操作時,為保證線程安全,使用寫時復制的實現

public E get(int index) {
    return elementAt(getArray(), index);
}
final Object[] getArray() {
    return array;
}
public boolean add(E e) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        es = Arrays.copyOf(es, len + 1);
        es[len] = e;
        setArray(es);
        return true;
    }
}
final void setArray(Object[] a) {
    array = a;
}

寫時復制也存在兩個問題,可以看到在add方法時使用了synchronized,也就是說當存在大量的寫入操作時,效率實際上是非常低的,另一個問題就是需要copy一份一模一樣的數據,可能會造成內存的異常波動,因此寫時復制實際上適用于讀多寫少的場景。

對比說明

public class ThreadSafeSet {
    public static void main(String[] args) throws InterruptedException {

        //Set<String> set = ConcurrentHashMap.newKeySet();

        //CopyOnWriteArraySet<String> set = new CopyOnWriteArraySet();

        readMoreWriteLess(set);

        System.out.println("==========華麗的分隔符==========");

        //set = ConcurrentHashMap.newKeySet();

        //set = new CopyOnWriteArraySet();

        writeMoreReadLess(set);
    }

    private static void writeMoreReadLess(Set<String> set) throws InterruptedException {
        //測20組
        for (int k = 1; k <= 20; k++) {
            CountDownLatch countDownLatch = new CountDownLatch(10);
            long s = System.currentTimeMillis();
            //創建9個線程,每個線程向set中寫1000條數據
            for (int i = 0; i < 9; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 1000; j++) {
                        set.add(UUID.randomUUID().toString());
                    }
                    countDownLatch.countDown();
                }).start();
            }

            //創建1個線程,每個線程從set中讀取所有數據,每個線程一共讀取10次。
            for (int i = 0; i < 1; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 10; j++) {
                        Iterator<String> iterator = set.iterator();
                        while (iterator.hasNext()) {
                            iterator.next();
                        }
                    }
                    countDownLatch.countDown();
                }).start();
            }
            //阻塞,直到10個線程都執行結束
            countDownLatch.await();
            long e = System.currentTimeMillis();
            System.out.println("寫多讀少:第" + k + "次執行耗時:" + (e - s) + "毫秒" + ",容器中元素個數為:" + set.size());
        }
    }

    private static void readMoreWriteLess(Set<String> set) throws InterruptedException {
        //測20組
        for (int k = 1; k <= 20; k++) {
            CountDownLatch countDownLatch = new CountDownLatch(10);
            long s = System.currentTimeMillis();
            //創建1個線程,每個線程向set中寫10條數據
            for (int i = 0; i < 1; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 10; j++) {
                        set.add(UUID.randomUUID().toString());
                    }
                    countDownLatch.countDown();
                }).start();
            }

            //創建9個線程,每個線程從set中讀取所有數據,每個線程一共讀取100萬次。
            for (int i = 0; i < 9; i++) {
                new Thread(() -> {
                    for (int j = 0; j < 1000000; j++) {
                        Iterator<String> iterator = set.iterator();
                        while (iterator.hasNext()) {
                            iterator.next();
                        }
                    }
                    countDownLatch.countDown();
                }).start();
            }
            countDownLatch.await();
            long e = System.currentTimeMillis();
            System.out.println("讀多寫少:第" + k + "次執行耗時:" + (e - s) + "毫秒" + ",容器中元素個數為:" + set.size());
        }
    }
}

經過測試可以發現在讀多寫少時CopyOnWriteArraySet會明顯優于ConcurrentHashMap.newKeySet(),但在寫多讀少時又會明顯弱于ConcurrentHashMap.newKeySet()。

當然使用CopyOnWriteArraySet還需要注意一點,寫入的數據可能不會被及時的讀取到,因為遍歷的是讀取之前獲取的快照。

這段代碼可以測試CopyOnWriteArraySet寫入數據不能被及時讀取到的問題。

public class COWSetTest {
    public static void main(String[] args) throws InterruptedException {
        CopyOnWriteArraySet<Integer> set = new CopyOnWriteArraySet();
        new Thread(() -> {
            try {
                set.add(1);
                System.out.println("第一個線程啟動,添加了一個元素,睡100毫秒");
                Thread.sleep(100);
                set.add(2);
                set.add(3);
                System.out.println("第一個線程添加了3個元素,執行結束");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        //保證讓第一個線程先執行
        Thread.sleep(1);

        new Thread(() -> {
            try {
                System.out.println("第二個線程啟動了!睡200毫秒");
                //Thread.sleep(200);//如果在這邊睡眠,可以獲取到3個元素
                Iterator<Integer> iterator = set.iterator();//生成快照
                Thread.sleep(200);//如果在這邊睡眠,只能獲取到1個元素
                while (iterator.hasNext()) {
                    System.out.println("第二個線程開始遍歷,獲取到元素:" + iterator.next());
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

    }
}

9. 異步

異步是提升系統響應能力的重要手段之一,異步思想的應用也非常的廣泛,常見的有:線程、MQ、事件通知、響應式編程等方式,有些概念在前面的章節中也涉及到了,異步最核心的思想就是,先快速接收,后查詢結果,比如:如果接口處理時間較長,那么可以優先響應中間狀態(處理中),然后提供回調和查詢接口,這樣就可以大大提升接口的吞吐量!

10. for循環優化

減少循環

通常可以通過一些高效的算法或者數據結構來減少循環次數,尤其當出現嵌套循環時要格外小心。常見的方式比如:有序的查找可以用二分,排序可以用快排,檢索可以構建Hash索引等等。

批量獲取

優化前:每次查詢一次數據庫;

for(String userId : userIds){
    User user = userMapper.queryById(userId);
    if(user.getName().equals("xxx")){
        // ...
    }
    
}

優化后:先批量查詢出來,再處理;

Map<String, User> userMap = userMapper.queryByIds(userIds);
for(String userId : userIds){
    User user = userMap.get(userId);
    if(user.getName().equals("xxx")){
        // ...
    }
}

緩存結果

優化前:每次都要根據每個用戶的roleId去數據庫查詢一次。

Map<String, User> userMap = userMapper.queryByIds(userIds);
for(String userId : userIds){
    User user = userMap.get(userId);
    Role role = roleMapper.queryById(user.getRoleId());
}

優化后:每次根據roleId查詢過以后就暫記下來,后面再遇到相同roleId時即可直接獲取,這比較適用于一次循環中roleId重復次數較多的場景。

Map<String, User> userMap = userMapper.queryByIds(userIds);
Map<String, Role> roleMap = new HashMap<>();
for(String userId : userIds){
    User user = userMap.get(userId);
    Role role = roleMap.get(user.getRoleId());
    if(role == null){
        role = roleMapper.queryById(user.getRoleId());
        roleMap.put(user.getRoleId(), role);
    }
}

并行處理

典型的如parallelStream

Integer sum = numbers.parallelStream().reduce(0, Integer::sum);

11. 減少網絡傳輸的體積

精簡字段

1.數據庫查詢時要避免頻繁查詢大文本字段,常見的如下面幾種:select url, describe, remark from t

2.接口傳輸時同樣要注意盡量減少內容傳輸的大小。

3.精簡字段除了通過減少不必要的字段傳輸之外,也可以通過改變數據結構,數據類型來實現。

數據傳輸格式

常用的如JSON,語法簡單,相比XML來說傳輸體積更小,解析更快,但如果需要頻繁傳輸大量數據時,使用protobuf則更會更加高效,因為其采用結構化的數據描述語言,并使用二進制編碼,因為體積更小,速度更快。

壓縮

常見的數據壓縮方式如:GZIP、zlib,而zip常用于文件壓縮。

借助Hutool工具包,可以看下壓縮的效果。

gzip壓縮

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}
System.out.println("壓縮前:" + sb.toString().getBytes().length);
byte[] compressedBytes = ZipUtil.gzip(sb.toString(), CharsetUtil.UTF_8);
System.out.println("壓縮后:" + compressedBytes.length);
String str = ZipUtil.unGzip(compressedBytes, CharsetUtil.UTF_8);
System.out.println("壓縮還原:" + str.getBytes().length);
壓縮前:2890
壓縮后:1474
壓縮還原:2890

zlib壓縮

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append(i);
}
System.out.println("壓縮前:" + sb.toString().getBytes().length);
byte[] compressedBytes = ZipUtil.zlib(sb.toString(), CharsetUtil.UTF_8, 1);
System.out.println("壓縮后:" + compressedBytes.length);
String str = ZipUtil.unZlib(compressedBytes, CharsetUtil.UTF_8);
System.out.println("壓縮還原:" + str.getBytes().length);
壓縮前:2890
壓縮后:1518
壓縮還原:2890

12. 減少服務之間的依賴

依賴越多,不但會給服務的穩定性、可靠性造成影響,同時也會成為性能提升的瓶頸,因此我們在設計之初就應當充分考慮到這個問題,通過合理的手段來減少服務之間的依賴。

鏈路治理

通過合理的微服務劃分,可以有效的減少鏈路上的依賴,鏈路調用之間要避免出現重復調用,循環依賴,以及上、下層級互相調用的情況。

重復調用

圖片圖片

循環依賴

圖片圖片

服務上、下層級混亂,互相調用

圖片圖片

數據冗余

數據冗余是指將非自身維護的數據通過某種手段保存下來,以便在之后使用時避免多次發起數據請求,從而實現減少服務依賴的手段。

常見的方式如:通用的基礎數據,字典數據等各個需求方可復制一份存在本地;建立寬表,冗余部分數據,減少關聯查詢。

結果緩存

將需要頻繁使用的結果存儲在緩存服務中,也是有效減少服務依賴的方式之一。

消息隊列

消息隊列天然就有簡化系統復雜性的作用,它通過異步的方式將任務與任務之間的關系進行解耦,也就達到了減少服務之間依賴的效果。

責任編輯:武曉燕 來源: 一安未來
相關推薦

2010-08-11 16:43:05

職場

2025-06-11 04:44:00

技巧Spring性能

2011-06-27 14:56:49

SEO

2018-09-21 14:32:00

iPaas云應用部署

2010-08-24 14:57:33

外企職場

2023-04-07 17:44:43

2013-05-10 09:23:14

iPaaS混合云集成云集成

2021-02-02 10:55:09

等級保護2.0信息安全網絡安全

2009-07-22 15:02:18

2009-10-13 16:38:04

強行關閉VMware虛

2011-06-24 17:23:30

網站優化

2009-01-03 09:14:00

2009-09-28 11:16:23

UPS電源

2013-12-18 11:34:17

云文件共享服務云文件同步服務BYOD

2017-03-13 15:39:09

Windows 10進程必殺技

2012-05-29 10:19:41

2016-08-31 10:48:51

新華三

2012-05-22 09:06:25

2014-07-14 10:39:37

云路由

2018-10-29 09:08:02

點贊
收藏

51CTO技術棧公眾號

一区二区视频免费观看| 蜜桃传媒一区二区亚洲av| 国产黄色片免费观看| 成人vr资源| 欧美一区二区三区在线看| 菠萝蜜视频在线观看入口| 日韩在线免费看| 久久精品国产一区二区| 久久久久亚洲精品国产| 日本55丰满熟妇厨房伦| 这里有精品可以观看| 91在线观看下载| 国产精品午夜视频| 国产无套在线观看| 98精品视频| 日韩av网站电影| 免费人成视频在线播放| 日韩三区在线| 国产午夜久久久久| 国产精品久久久久91| 国产一级大片在线观看| 日本黄色精品| 538prom精品视频线放| 在线精品日韩| 美女做暖暖视频免费在线观看全部网址91| 99pao成人国产永久免费视频| 亚洲国产成人久久综合| 爱豆国产剧免费观看大全剧苏畅| 女人高潮被爽到呻吟在线观看| 91亚洲精品久久久蜜桃网站 | 成人在线不卡| 精品久久久久国产| 91成人综合网| 香港一级纯黄大片| 日韩电影在线观看一区| 国模gogo一区二区大胆私拍| 人人妻人人藻人人爽欧美一区| 亚洲精品一区二区三区在线| 欧美日韩国产色| 日韩欧美精品一区二区三区经典| 中文字幕人妻一区二区三区视频| 性色一区二区三区| 午夜精品美女自拍福到在线| www.男人天堂| 国产一区一一区高清不卡| 亚洲人成小说网站色在线| 天天爽天天狠久久久| 女人天堂在线| 国产亚洲精品资源在线26u| 久久波多野结衣| 91国在线视频| 久久超级碰视频| 韩国19禁主播vip福利视频| 丁香花五月激情| 美女久久久久| 亚洲性av在线| 色悠悠在线视频| 成人线上播放| 欧美美女直播网站| 久久99中文字幕| 丁香在线视频| caoporen国产精品视频| 国产精品推荐精品| 涩涩视频免费看| 91美女片黄在线观看91美女| 91在线看www| 国产免费一级视频| 国产精品久久国产愉拍| 日韩美女在线观看| 国产精品xxxxxx| 亚洲美女一区| 欧美美女操人视频| 久久高清免费视频| 日韩精品一卡二卡三卡四卡无卡| 久久6免费高清热精品| 免费一级片在线观看| 日本一区二区免费高清| 久久综合久久八八| 久久免费精彩视频| 久久国产欧美| 国产主播精品在线| 欧美自拍第一页| 国产麻豆一精品一av一免费| 国产精品女人久久久久久| 日本熟妇一区二区| 亚洲视频观看| 国产成人精品一区二区在线| 国产精品怡红院| av在线播放不卡| 国产福利一区二区三区在线观看| 91在线你懂的| 成人午夜精品一区二区三区| 91人人爽人人爽人人精88v| 亚洲精品国产一区二| 久久精品一区八戒影视| 国产乱码精品一区二区三区卡| 黄色在线观看网| 久久九九99视频| 看高清中日韩色视频| 日本在线播放| 综合久久久久久久| 99久re热视频精品98| 蜜桃视频m3u8在线观看| 欧美日韩成人综合天天影院| 亚洲香蕉中文网| 欧美亚洲国产日韩| 久久久国产一区二区| 国产极品美女在线| 性高湖久久久久久久久| 3d动漫精品啪啪一区二区三区免费 | 亚洲福利影片在线| 日韩av无码一区二区三区不卡| 草草视频在线一区二区| 少妇高潮久久77777| 国产成人在线免费观看视频| 久久99国产精品免费网站| 久久久亚洲综合网站| 宅男在线观看免费高清网站| 欧美在线观看一二区| 亚欧美在线观看| 国产精品毛片无码| 精品人在线二区三区| 性爱在线免费视频| 亚洲欧美日韩综合国产aⅴ| 国产v综合v亚洲欧美久久 | 日韩黄色高清视频| 欧洲av一区二区三区| 精品动漫一区| 国产精品com| 亚洲欧美丝袜中文综合| 一区二区三区四区不卡视频| 涩涩网站在线看| swag国产精品一区二区| 日韩精品在线电影| 日韩女同强女同hd| 蜜臀久久99精品久久久久久9| 国新精品乱码一区二区三区18 | 久久久久久成人| 99久久久无码国产精品免费| 国产精品理论在线观看| www.亚洲成人网| 国产乱码精品一区二区三区亚洲人 | 91黄色小视频| 亚洲一区二区偷拍| 操欧美老女人| 国产精品视频公开费视频| 国外av在线| 在线视频国内一区二区| 337p日本欧洲亚洲大胆张筱雨| 久久精品国内一区二区三区水蜜桃| 国产精品自产拍在线观| 人妻妺妺窝人体色www聚色窝| 亚洲精品国产视频| 黄色片子免费看| 欧美福利一区| 国产精品久久久久久久久久东京| 九九九伊在人线综合| 一区二区三区日韩欧美精品| 黄页网站在线看| 亚洲日本成人| 亚洲一区二区三区乱码aⅴ蜜桃女| 日韩在线一区二区三区四区| 天天综合色天天综合| 亚洲自拍第三页| 红桃视频国产精品| 91精品久久久久久久| 日韩电影在线观看完整版| 色婷婷狠狠综合| 欧美福利在线视频| 国产精品白丝jk白祙喷水网站 | 91视频青青草| 成人在线一区二区三区| 亚洲五月六月| 日本欧美不卡| 久久天天躁狠狠躁夜夜躁2014| 无码人妻丰满熟妇精品| www.性欧美| 国产三级三级三级看三级| 97视频精品| 国产精品视频yy9099| a级毛片免费观看在线| 欧美丰满高潮xxxx喷水动漫| 国产性70yerg老太| 国产精品亚洲一区二区三区妖精| 免费国产a级片| 日韩成人免费| 国产精品嫩草影院久久久| www在线免费观看视频| 精品区一区二区| 麻豆视频免费在线播放| 国产又粗又猛又爽又黄91精品| 婷婷无套内射影院| 日韩一级毛片| 国产一区二区丝袜高跟鞋图片| 国产黄在线观看免费观看不卡| 91麻豆精品国产91久久久久久| 日韩三级免费看| 亚洲欧洲日产国码二区| 人人妻人人澡人人爽人人精品| 韩国三级中文字幕hd久久精品| 亚洲一区3d动漫同人无遮挡 | 91tv亚洲精品香蕉国产一区| 欧美日韩第一视频| av在线电影免费观看| 在线观看国产日韩| 摸摸摸bbb毛毛毛片| 奇米一区二区三区| 国产精品无码人妻一区二区在线| jizz性欧美23| 成人羞羞国产免费| 国产在线xxx| 俺去啦;欧美日韩| 国产叼嘿视频在线观看| 亚洲另类在线制服丝袜| 日韩一级av毛片| 激情欧美一区二区| 黄色高清无遮挡| 亚洲春色h网| 国产福利视频一区| 乱人伦视频在线| 色小说视频一区| 国产毛片毛片毛片毛片| 色婷婷国产精品| av大全在线观看| 亚洲三级在线看| 五月婷婷综合激情网| 国产成人在线影院| 国产一区二区在线观看免费视频| 视频在线在亚洲| avav在线看| 久久国产中文字幕| 日韩精品电影网站| 免费看日本一区二区| 成人免费激情视频| 成人高潮aa毛片免费| 亚洲欧美国产视频| 伊人久久亚洲综合| 亚洲狠狠爱一区二区三区| 一区二区视频免费看| 中文字幕一区日韩精品欧美| 日本少妇激三级做爰在线| 久久激情五月激情| 亚洲精品综合在线观看| 免费av网站大全久久| 天天爽夜夜爽一区二区三区| 欧美日韩1080p| 欧美一区1区三区3区公司 | 亚洲国产综合在线观看| 韩国日本不卡在线| 欧美aaaaaaa| 国产一区二区三区在线| 国产精品一级伦理| 亚洲精品在线免费播放| 亚洲AV无码一区二区三区性| 精品人伦一区二区色婷婷| 人妻视频一区二区三区| 日韩电影第一页| 国产福利第一页| 在线观看一区二区视频| 日韩av在线电影| 一区二区三区在线影院| 九九视频免费在线观看| 国产女人aaa级久久久级| 天堂www中文在线资源| 韩国女主播成人在线| 波多野结衣中文字幕在线播放| 国产传媒一区在线| 四季av一区二区三区| 亚洲黄色毛片| 欧美aⅴ在线观看| 18成人免费观看视频| 一本一道久久a久久精品综合| 99热国内精品| 日本五级黄色片| 噜噜爱69成人精品| 少妇人妻无码专区视频| 久久婷婷一区| 91av免费观看| 国产曰批免费观看久久久| 国产精品区在线| 国产成人超碰人人澡人人澡| 自拍视频一区二区| 国产精品福利电影一区二区三区四区| 91成人福利视频| 色综合天天天天做夜夜夜夜做| 中文无码av一区二区三区| 日韩欧美色综合| 美女欧美视频在线观看免费 | 日韩电影中文 亚洲精品乱码| 国产高清免费av| 欧美疯狂做受xxxx富婆| 欧美视频一二区| 亚洲成人久久久久| 第一福利在线| 欧美激情免费观看| 欧美舌奴丨vk视频| 久久久久久久久爱| 成人在线观看免费播放| 国产精品香蕉视屏| 欧美顶级毛片在线播放| 一区二区不卡在线| 五月激情综合| 亚洲精品中文字幕无码蜜桃| 久久婷婷丁香| 成人做爰69片免费| 国产精品久久久久aaaa樱花| 国产精品夜夜夜爽阿娇| 欧美日韩在线一区| 亚洲精品国产精品国| 日韩最新av在线| 国产激情视频在线观看| 国产成人精品av在线| 久久亚洲精品爱爱| 玛丽玛丽电影原版免费观看1977 | 自拍偷拍视频在线| 欧美亚洲视频| 国产一卡二卡三卡四卡| 国产一区二区三区在线观看免费视频| 加勒比av中文字幕| 国产欧美日韩一区二区三区在线观看| 国产精品19乱码一区二区三区| 精品国产精品自拍| 成人午夜福利视频| 国产一区二区三区免费视频| √8天堂资源地址中文在线| 92看片淫黄大片看国产片| 成人女性视频| 狠狠热免费视频| 国产精品99久久久| tube国产麻豆| 欧美一区二区视频网站| 国产美女福利在线| 成人免费观看a| 亚洲va久久久噜噜噜久久| 亚洲一区影院| 亚洲精品看片| 色婷婷狠狠18| 不卡的看片网站| 久久久久久久伊人| 亚洲成av人片在线观看香蕉| 久色国产在线| 国产欧美 在线欧美| a级日韩大片| 亚洲激情啪啪| 精品亚洲成a人| 成人黄色短视频| 欧美人牲a欧美精品| 牛牛澡牛牛爽一区二区| 欧美巨大黑人极品精男| 久久在线观看| 韩日视频在线观看| 99久久伊人精品| 丝袜 亚洲 另类 欧美 重口 | 三级国产在线观看| 日本a级片电影一区二区| 奇米亚洲欧美| 国产精品v日韩精品v在线观看| 日韩理论在线观看| 九九精品免费视频| 亚洲色图校园春色| 欧美成人aaa| 国产av第一区| 蜜臀av性久久久久蜜臀av麻豆| 欧产日产国产精品98| 亚洲成国产人片在线观看| 天堂中文在线资| 久久91精品国产91久久久| 欧美极品在线| 日本一区二区三区免费观看 | 亚洲成人激情小说| 午夜精品久久久久久久99水蜜桃| 91成年人视频| 欧美激情伊人电影| 美女久久99| 成人免费aaa| 国产精品久久久久久久久久免费看 | 一本久道久久久| 中文字幕第24页| 欧美电影免费提供在线观看| 日本不卡1234视频| 一区二区三区不卡在线| 成人小视频免费观看| 波多野结衣毛片| 亚洲欧美成人在线| 国产精一区二区| 欧美激情成人网| 亚洲人成小说网站色在线| 国产精品高潮呻吟AV无码| 久久久久久亚洲精品不卡| 2023国产精华国产精品| 亚洲国产精品毛片av不卡在线| 成人不卡免费av| 黄色一级片免费看| 日韩在线视频国产| 欧美三级午夜理伦三级在线观看 | 丰满熟女人妻一区二区三| 亚洲欧美综合区自拍另类| 成人自拍视频| 青青在线视频免费| 午夜视频一区二区|