日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区

您的位置:首頁技術文章
文章詳情頁

Java ThreadLocal的使用場景總結

瀏覽:59日期:2022-08-12 17:37:17
目錄使用場景1:本地變量① 2個線程格式化② 10個線程格式化③ 1000個線程格式化a) 線程安全問題分析b) 解決線程安全問題:加鎖c) 解決線程安全問題:ThreadLocal1.ThreadLocal 介紹2.ThreadLocal 基礎使用3.ThreadLocal 高級用法4.ThreadLocal 版時間格式化使用場景2:跨類傳遞數據總結使用場景1:本地變量

我們以多線程格式化時間為例,來演示 ThreadLocal 的價值和作用,當我們在多個線程中格式化時間時,通常會這樣操作。

① 2個線程格式化

當有 2 個線程進行時間格式化時,我們可以這樣寫:

import java.text.SimpleDateFormat;import java.util.Date;public class Test { public static void main(String[] args) throws InterruptedException {// 創建并啟動線程1Thread t1 = new Thread(new Runnable() { @Override public void run() {// 得到時間對象Date date = new Date(1 * 1000);// 執行時間格式化formatAndPrint(date); }});t1.start();// 創建并啟動線程2Thread t2 = new Thread(new Runnable() { @Override public void run() {// 得到時間對象Date date = new Date(2 * 1000);// 執行時間格式化formatAndPrint(date); }});t2.start(); } /** * 格式化并打印結果 * @param date 時間對象 */ private static void formatAndPrint(Date date) {// 格式化時間對象SimpleDateFormat simpleDateFormat = new SimpleDateFormat('mm:ss');// 執行格式化String result = simpleDateFormat.format(date);// 打印最終結果System.out.println('時間:' + result); }}

以上程序的執行結果為:

Java ThreadLocal的使用場景總結

上面的代碼因為創建的線程數量并不多,所以我們可以給每個線程創建一個私有對象 SimpleDateFormat 來進行時間格式化。

② 10個線程格式化

當線程的數量從 2 個升級為 10 個時,我們可以使用 for 循環來創建多個線程執行時間格式化,具體實現代碼如下:

import java.text.SimpleDateFormat;import java.util.Date;public class Test { public static void main(String[] args) throws InterruptedException {for (int i = 0; i < 10; i++) { int finalI = i; // 創建線程 Thread thread = new Thread(new Runnable() {@Overridepublic void run() { // 得到時間對象 Date date = new Date(finalI * 1000); // 執行時間格式化 formatAndPrint(date);} }); // 啟動線程 thread.start();} } /** * 格式化并打印時間 * @param date 時間對象 */ private static void formatAndPrint(Date date) {// 格式化時間對象SimpleDateFormat simpleDateFormat = new SimpleDateFormat('mm:ss');// 執行格式化String result = simpleDateFormat.format(date);// 打印最終結果System.out.println('時間:' + result); }}

以上程序的執行結果為:

Java ThreadLocal的使用場景總結

從上述結果可以看出,雖然此時創建的線程數和 SimpleDateFormat 的數量不算少,但程序還是可以正常運行的。

③ 1000個線程格式化

然而當我們將線程的數量從 10 個變成 1000 個的時候,我們就不能單純的使用 for 循環來創建 1000 個線程的方式來解決問題了,因為這樣頻繁的新建和銷毀線程會造成大量的系統開銷和線程過度爭搶 CPU 資源的問題。

所以經過一番思考后,我們決定使用線程池來執行這 1000 次的任務,因為線程池可以復用線程資源,無需頻繁的新建和銷毀線程,也可以通過控制線程池中線程的數量來避免過多線程所導致的 **CPU** 資源過度爭搶和線程頻繁切換所造成的性能問題,而且我們可以將 SimpleDateFormat 提升為全局變量,從而避免每次執行都要新建 SimpleDateFormat 的問題,于是我們寫下了這樣的代碼:

import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class App { // 時間格式化對象 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat('mm:ss'); public static void main(String[] args) throws InterruptedException {// 創建線程池執行任務ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));for (int i = 0; i < 1000; i++) { int finalI = i; // 執行任務 threadPool.execute(new Runnable() {@Overridepublic void run() { // 得到時間對象 Date date = new Date(finalI * 1000); // 執行時間格式化 formatAndPrint(date);} });}// 線程池執行完任務之后關閉threadPool.shutdown(); } /** * 格式化并打印時間 * @param date 時間對象 */ private static void formatAndPrint(Date date) {// 執行格式化String result = simpleDateFormat.format(date);// 打印最終結果System.out.println('時間:' + result); }}

以上程序的執行結果為:

Java ThreadLocal的使用場景總結

當我們懷著無比喜悅的心情去運行程序的時候,卻發現意外發生了,這樣寫代碼竟然會出現線程安全的問題。從上述結果可以看出,程序的打印結果竟然有重復內容的,正確的情況應該是沒有重復的時間才對。

PS:所謂的線程安全問題是指:在多線程的執行中,程序的執行結果與預期結果不相符的情況。

a) 線程安全問題分析

為了找到問題所在,我們嘗試查看 SimpleDateFormat 中 format 方法的源碼來排查一下問題,format 源碼如下:

private StringBuffer format(Date date, StringBuffer toAppendTo,FieldDelegate delegate) { // 注意此行代碼 calendar.setTime(date); boolean useDateFormatSymbols = useDateFormatSymbols(); for (int i = 0; i < compiledPattern.length; ) {int tag = compiledPattern[i] >>> 8;int count = compiledPattern[i++] & 0xff;if (count == 255) { count = compiledPattern[i++] << 16; count |= compiledPattern[i++];}switch (tag) { case TAG_QUOTE_ASCII_CHAR:toAppendTo.append((char)count);break; case TAG_QUOTE_CHARS:toAppendTo.append(compiledPattern, i, count);i += count;break; default:subFormat(tag, count, delegate, toAppendTo, useDateFormatSymbols);break;} } return toAppendTo;}

從上述源碼可以看出,在執行 SimpleDateFormat.format 方法時,會使用 calendar.setTime 方法將輸入的時間進行轉換,那么我們想象一下這樣的場景:

線程 1 執行了 calendar.setTime(date) 方法,將用戶輸入的時間轉換成了后面格式化時所需要的時間; 線程 1 暫停執行,線程 2 得到 CPU 時間片開始執行; 線程 2 執行了 calendar.setTime(date) 方法,對時間進行了修改; 線程 2 暫停執行,線程 1 得出 CPU 時間片繼續執行,因為線程 1 和線程 2 使用的是同一對象,而時間已經被線程 2 修改了,所以此時當線程 1 繼續執行的時候就會出現線程安全的問題了。

正常的情況下,程序的執行是這樣的:

Java ThreadLocal的使用場景總結

非線程安全的執行流程是這樣的:

Java ThreadLocal的使用場景總結

b) 解決線程安全問題:加鎖

當出現線程安全問題時,我們想到的第一解決方案就是加鎖,具體的實現代碼如下:

import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class App { // 時間格式化對象 private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat('mm:ss'); public static void main(String[] args) throws InterruptedException {// 創建線程池執行任務ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));for (int i = 0; i < 1000; i++) { int finalI = i; // 執行任務 threadPool.execute(new Runnable() {@Overridepublic void run() { // 得到時間對象 Date date = new Date(finalI * 1000); // 執行時間格式化 formatAndPrint(date);} });}// 線程池執行完任務之后關閉threadPool.shutdown(); } /** * 格式化并打印時間 * @param date 時間對象 */ private static void formatAndPrint(Date date) {// 執行格式化String result = null;// 加鎖synchronized (App.class) { result = simpleDateFormat.format(date);}// 打印最終結果System.out.println('時間:' + result); }}

以上程序的執行結果為:

Java ThreadLocal的使用場景總結

從上述結果可以看出,使用了 synchronized 加鎖之后程序就可以正常的執行了。

加鎖的缺點

加鎖的方式雖然可以解決線程安全的問題,但同時也帶來了新的問題,當程序加鎖之后,所有的線程必須排隊執行某些業務才行,這樣無形中就降低了程序的運行效率了。

有沒有既能解決線程安全問題,又能提高程序的執行速度的解決方案呢?

答案是:有的,這個時候 ThreadLocal 就要上場了。

c) 解決線程安全問題:ThreadLocal1.ThreadLocal 介紹

ThreadLocal 從字面的意思來理解是線程本地變量的意思,也就是說它是線程中的私有變量,每個線程只能使用自己的變量。

以上面線程池格式化時間為例,當線程池中有 10 個線程時,SimpleDateFormat 會存入 ThreadLocal 中,它也只會創建 10 個對象,即使要執行 1000 次時間格式化任務,依然只會新建 10 個 SimpleDateFormat 對象,每個線程調用自己的 ThreadLocal 變量。

2.ThreadLocal 基礎使用

ThreadLocal 常用的核心方法有三個:

set 方法:用于設置線程獨立變量副本。 沒有 set 操作的 ThreadLocal 容易引起臟數據。 get 方法:用于獲取線程獨立變量副本。 沒有 get 操作的 ThreadLocal 對象沒有意義。 remove 方法:用于移除線程獨立變量副本。 沒有 remove 操作容易引起內存泄漏。

ThreadLocal 所有方法如下圖所示:

Java ThreadLocal的使用場景總結

官方說明文檔:https://docs.oracle.com/javase/8/docs/api/

ThreadLocal 基礎用法如下:

/** * @公眾號:Java中文社群 */public class ThreadLocalExample { // 創建一個 ThreadLocal 對象 private static ThreadLocal<String> threadLocal = new ThreadLocal<>(); public static void main(String[] args) {// 線程執行任務Runnable runnable = new Runnable() { @Override public void run() {String threadName = Thread.currentThread().getName();System.out.println(threadName + ' 存入值:' + threadName);// 在 ThreadLocal 中設置值threadLocal.set(threadName);// 執行方法,打印線程中設置的值print(threadName); }};// 創建并啟動線程 1new Thread(runnable, 'MyThread-1').start();// 創建并啟動線程 2new Thread(runnable, 'MyThread-2').start(); } /** * 打印線程中的 ThreadLocal 值 * @param threadName 線程名稱 */ private static void print(String threadName) {try { // 得到 ThreadLocal 中的值 String result = threadLocal.get(); // 打印結果 System.out.println(threadName + ' 取出值:' + result);} finally { // 移除 ThreadLocal 中的值(防止內存溢出) threadLocal.remove();} }}

以上程序的執行結果為:

Java ThreadLocal的使用場景總結

從上述結果可以看出,每個線程只會讀取到屬于自己的 ThreadLocal 值。

3.ThreadLocal 高級用法

① 初始化:initialValue

public class ThreadLocalByInitExample { // 定義 ThreadLocal private static ThreadLocal<String> threadLocal = new ThreadLocal(){@Overrideprotected String initialValue() { System.out.println('執行 initialValue() 方法'); return '默認值';} }; public static void main(String[] args) {// 線程執行任務Runnable runnable = new Runnable() { @Override public void run() {// 執行方法,打印線程中數據(未設置值打印)print(threadName); }};// 創建并啟動線程 1new Thread(runnable, 'MyThread-1').start();// 創建并啟動線程 2new Thread(runnable, 'MyThread-2').start(); } /** * 打印線程中的 ThreadLocal 值 * @param threadName 線程名稱 */ private static void print(String threadName) {// 得到 ThreadLocal 中的值String result = threadLocal.get();// 打印結果System.out.println(threadName + ' 得到值:' + result); }}

以上程序的執行結果為:

Java ThreadLocal的使用場景總結

當使用了 #threadLocal.set 方法之后,initialValue 方法就不會被執行了,如下代碼所示:

public class ThreadLocalByInitExample { // 定義 ThreadLocal private static ThreadLocal<String> threadLocal = new ThreadLocal() {@Overrideprotected String initialValue() { System.out.println('執行 initialValue() 方法'); return '默認值';} }; public static void main(String[] args) {// 線程執行任務Runnable runnable = new Runnable() { @Override public void run() {String threadName = Thread.currentThread().getName();System.out.println(threadName + ' 存入值:' + threadName);// 在 ThreadLocal 中設置值threadLocal.set(threadName);// 執行方法,打印線程中設置的值print(threadName); }};// 創建并啟動線程 1new Thread(runnable, 'MyThread-1').start();// 創建并啟動線程 2new Thread(runnable, 'MyThread-2').start(); } /** * 打印線程中的 ThreadLocal 值 * @param threadName 線程名稱 */ private static void print(String threadName) {try { // 得到 ThreadLocal 中的值 String result = threadLocal.get(); // 打印結果 System.out.println(threadName + '取出值:' + result);} finally { // 移除 ThreadLocal 中的值(防止內存溢出) threadLocal.remove();} }}

以上程序的執行結果為:

Java ThreadLocal的使用場景總結

為什么 set 方法之后,初始化代碼就不執行了?要理解這個問題,需要從 ThreadLocal.get() 方法的源碼中得到答案,因為初始化方法 initialValue 在 ThreadLocal 創建時并不會立即執行,而是在調用了 get 方法只會才會執行,測試代碼如下:

import java.util.Date;public class ThreadLocalByInitExample { // 定義 ThreadLocal private static ThreadLocal<String> threadLocal = new ThreadLocal() {@Overrideprotected String initialValue() { System.out.println('執行 initialValue() 方法 ' + new Date()); return '默認值';} }; public static void main(String[] args) {// 線程執行任務Runnable runnable = new Runnable() { @Override public void run() {// 得到當前線程名稱String threadName = Thread.currentThread().getName();// 執行方法,打印線程中設置的值print(threadName); }};// 創建并啟動線程 1new Thread(runnable, 'MyThread-1').start();// 創建并啟動線程 2new Thread(runnable, 'MyThread-2').start(); } /** * 打印線程中的 ThreadLocal 值 * @param threadName 線程名稱 */ private static void print(String threadName) {System.out.println('進入 print() 方法 ' + new Date());try { // 休眠 1s Thread.sleep(1000);} catch (InterruptedException e) { e.printStackTrace();}// 得到 ThreadLocal 中的值String result = threadLocal.get();// 打印結果System.out.println(String.format('%s 取得值:%s %s',threadName, result, new Date())); }}

以上程序的執行結果為:

Java ThreadLocal的使用場景總結

從上述打印的時間可以看出:initialValue 方法并不是在 ThreadLocal 創建時執行的,而是在調用 Thread.get 方法時才執行的。

接下來來看 Threadlocal.get 源碼的實現:

public T get() { // 得到當前的線程 Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); // 判斷 ThreadLocal 中是否有數據 if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) { @SuppressWarnings('unchecked') T result = (T)e.value; // 有 set 值,直接返回數據 return result;} } // 執行初始化方法【重點關注】 return setInitialValue();}private T setInitialValue() { // 執行初始化方法【重點關注】 T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null)map.set(this, value); elsecreateMap(t, value); return value;}

從上述源碼可以看出,當 ThreadLocal 中有值時會直接返回值 e.value,只有 Threadlocal 中沒有任何值時才會執行初始化方法 initialValue。

注意事項—類型必須保持一致注意在使用 initialValue 時,返回值的類型要和 ThreadLoca 定義的數據類型保持一致,如下圖所示:

Java ThreadLocal的使用場景總結

如果數據不一致就會造成 ClassCaseException 類型轉換異常,如下圖所示:

Java ThreadLocal的使用場景總結

② 初始化2:withInitial

import java.util.function.Supplier;public class ThreadLocalByInitExample { // 定義 ThreadLocal private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(new Supplier<String>() {@Overridepublic String get() { System.out.println('執行 withInitial() 方法'); return '默認值';} }); public static void main(String[] args) {// 線程執行任務Runnable runnable = new Runnable() { @Override public void run() {String threadName = Thread.currentThread().getName();// 執行方法,打印線程中設置的值print(threadName); }};// 創建并啟動線程 1new Thread(runnable, 'MyThread-1').start();// 創建并啟動線程 2new Thread(runnable, 'MyThread-2').start(); } /** * 打印線程中的 ThreadLocal 值 * @param threadName 線程名稱 */ private static void print(String threadName) {// 得到 ThreadLocal 中的值String result = threadLocal.get();// 打印結果System.out.println(threadName + ' 得到值:' + result); }}

通過上述的代碼發現,withInitial 方法的使用好和 initialValue 好像沒啥區別,那為啥還要造出兩個類似的方法呢?客官莫著急,繼續往下看。

③ 更簡潔的 withInitial 使用withInitial 方法的優勢在于可以更簡單的實現變量初始化,如下代碼所示:

public class ThreadLocalByInitExample { // 定義 ThreadLocal private static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(() -> '默認值'); public static void main(String[] args) {// 線程執行任務Runnable runnable = new Runnable() { @Override public void run() {String threadName = Thread.currentThread().getName();// 執行方法,打印線程中設置的值print(threadName); }};// 創建并啟動線程 1new Thread(runnable, 'MyThread-1').start();// 創建并啟動線程 2new Thread(runnable, 'MyThread-2').start(); } /** * 打印線程中的 ThreadLocal 值 * @param threadName 線程名稱 */ private static void print(String threadName) {// 得到 ThreadLocal 中的值String result = threadLocal.get();// 打印結果System.out.println(threadName + ' 得到值:' + result); }}

以上程序的執行結果為:

Java ThreadLocal的使用場景總結

4.ThreadLocal 版時間格式化

了解了 ThreadLocal 的使用之后,我們回到本文的主題,接下來我們將使用 ThreadLocal 來實現 1000 個時間的格式化,具體實現代碼如下:

import java.text.SimpleDateFormat;import java.util.Date;import java.util.concurrent.LinkedBlockingQueue;import java.util.concurrent.ThreadPoolExecutor;import java.util.concurrent.TimeUnit;public class MyThreadLocalByDateFormat { // 創建 ThreadLocal 并設置默認值 private static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = ThreadLocal.withInitial(() -> new SimpleDateFormat('mm:ss')); public static void main(String[] args) {// 創建線程池執行任務ThreadPoolExecutor threadPool = new ThreadPoolExecutor(10, 10, 60,TimeUnit.SECONDS, new LinkedBlockingQueue<>(1000));// 執行任務for (int i = 0; i < 1000; i++) { int finalI = i; // 執行任務 threadPool.execute(new Runnable() {@Overridepublic void run() { // 得到時間對象 Date date = new Date(finalI * 1000); // 執行時間格式化 formatAndPrint(date);} });}// 線程池執行完任務之后關閉threadPool.shutdown();// 線程池執行完任務之后關閉threadPool.shutdown(); } /** * 格式化并打印時間 * @param date 時間對象 */ private static void formatAndPrint(Date date) {// 執行格式化String result = dateFormatThreadLocal.get().format(date);// 打印最終結果System.out.println('時間:' + result); }}

以上程序的執行結果為:

Java ThreadLocal的使用場景總結

從上述結果可以看出,使用 ThreadLocal 也可以解決線程并發問題,并且避免了代碼加鎖排隊執行的問題。

使用場景2:跨類傳遞數據

除了上面的使用場景之外,我們還可以使用 **ThreadLocal** 來實現線程中跨類、跨方法的數據傳遞。比如登錄用戶的 User 對象信息,我們需要在不同的子系統中多次使用,如果使用傳統的方式,我們需要使用方法傳參和返回值的方式來傳遞 User 對象,然而這樣就無形中造成了類和類之間,甚至是系統和系統之間的相互耦合了,所以此時我們可以使用 ThreadLocal 來實現 User 對象的傳遞。

確定了方案之后,接下來我們來實現具體的業務代碼。我們可以先在主線程中構造并初始化一個 User 對象,并將此 User 對象存儲在 ThreadLocal 中,存儲完成之后,我們就可以在同一個線程的其他類中,如倉儲類或訂單類中直接獲取并使用 User 對象了,具體實現代碼如下。

主線程中的業務代碼:

public class ThreadLocalByUser { public static void main(String[] args) {// 初始化用戶信息User user = new User('Java');// 將 User 對象存儲在 ThreadLocal 中UserStorage.setUser(user);// 調用訂單系統OrderSystem orderSystem = new OrderSystem();// 添加訂單(方法內獲取用戶信息)orderSystem.add();// 調用倉儲系統RepertorySystem repertory = new RepertorySystem();// 減庫存(方法內獲取用戶信息)repertory.decrement(); }}

User 實體類:

/** * 用戶實體類 */class User { public User(String name) {this.name = name; } private String name; public String getName() {return name; } public void setName(String name) {this.name = name; }}

ThreadLocal 操作類:

/** * 用戶信息存儲類 */class UserStorage { // 用戶信息 public static ThreadLocal<User> USER = new ThreadLocal(); /** * 存儲用戶信息 * @param user 用戶數據 */ public static void setUser(User user) {USER.set(user); }}

訂單類:

/** * 訂單類 */class OrderSystem { /** * 訂單添加方法 */ public void add() {// 得到用戶信息User user = UserStorage.USER.get();// 業務處理代碼(忽略)...System.out.println(String.format('訂單系統收到用戶:%s 的請求。',user.getName())); }}

倉儲類:

/** * 倉儲類 */class RepertorySystem { /** * 減庫存方法 */ public void decrement() {// 得到用戶信息User user = UserStorage.USER.get();// 業務處理代碼(忽略)...System.out.println(String.format('倉儲系統收到用戶:%s 的請求。',user.getName())); }}

以上程序的最終執行結果:

Java ThreadLocal的使用場景總結

從上述結果可以看出,當我們在主線程中先初始化了 User 對象之后,訂單類和倉儲類無需進行任何的參數傳遞也可以正常獲得 User 對象了,從而實現了一個線程中,跨類和跨方法的數據傳遞。

總結

使用 ThreadLocal 可以創建線程私有變量,所以不會導致線程安全問題,同時使用 ThreadLocal 還可以避免因為引入鎖而造成線程排隊執行所帶來的性能消耗;再者使用 ThreadLocal 還可以實現一個線程內跨類、跨方法的數據傳遞。

以上就是Java ThreadLocal的使用場景總結的詳細內容,更多關于Java ThreadLocal的使用場景的資料請關注好吧啦網其它相關文章!

標簽: Java
相關文章:
日本不卡不码高清免费观看,久久国产精品久久w女人spa,黄色aa久久,三上悠亚国产精品一区二区三区
韩国精品主播一区二区在线观看 | 日韩三级久久| 老司机精品视频网| 涩涩涩久久久成人精品| 亚洲一区二区三区高清不卡| 国产成人精品亚洲线观看 | 国产精品嫩草影院在线看| 亚洲二区视频| 合欧美一区二区三区| 日本欧美不卡| 亚洲一区久久| 日韩高清二区| 国产精品亚洲片在线播放| 91欧美极品| 精品日韩一区| 91日韩免费| 久久蜜桃资源一区二区老牛| 亚洲婷婷在线| jiujiure精品视频播放| 在线观看免费一区二区| 亚洲精品亚洲人成在线观看| 首页国产精品| 夜久久久久久| 国产精品v亚洲精品v日韩精品| 麻豆国产精品视频| 在线观看精品| 美女精品在线观看| 久久爱www成人| 国产一区欧美| 国产伦理久久久久久妇女| 国产在线一区不卡| 亚洲国产成人精品女人| 亚洲色图综合| 麻豆mv在线观看| 欧美日韩在线二区| 欧美亚洲tv| 亚洲天堂成人| 美女视频黄免费的久久| 久久精品91| 日韩精品中文字幕吗一区二区| 国产高清视频一区二区| 91久久午夜| 久久精品九色| 日本亚洲欧美天堂免费| 免费污视频在线一区| 国产精品亚洲二区| 视频一区欧美日韩| 人在线成免费视频| 国产亚洲久久| 精品一区在线| 日韩免费在线| 精品久久久中文字幕| 亚洲精品精选| 国产亚洲精品久久久久婷婷瑜伽| 日韩不卡一区| 国产欧美亚洲精品a| 亚洲一级大片| 丝袜美腿亚洲色图| 久久中文字幕av一区二区不卡| 国产精品入口久久| 欧美一级网站| 国产欧美自拍| 国产精品一区三区在线观看| 中文字幕av一区二区三区人| 日韩欧美一区二区三区在线视频 | 麻豆极品一区二区三区| 国产精品视频一区二区三区四蜜臂 | 色爱av综合网| 久久九九精品| 精品免费视频| 成人黄色av| 91精品婷婷色在线观看| 久久天堂精品| 91成人精品| 亚洲精品高潮| 国产精品欧美大片| 97精品一区| 国产精品88久久久久久| 免费人成在线不卡| 热久久久久久| 久久精品国产在热久久| av高清一区| 亚洲制服一区| 麻豆国产精品777777在线| 九九99久久精品在免费线bt| 国产成人精品福利| 激情婷婷综合| 天堂久久av| 国产日韩电影| 午夜在线一区二区| 久久国产精品免费精品3p | 成人在线超碰| 亚洲天堂黄色| 欧美色综合网| 99久久久国产精品美女| 好看的av在线不卡观看| 日本精品国产| 蜜桃视频欧美| 精品女同一区二区三区在线观看| 欧美福利专区| 牛牛精品成人免费视频| 不卡中文一二三区| 国产精品主播| 丝袜美腿高跟呻吟高潮一区| 精品深夜福利视频| 亚洲尤物av| 黑丝美女一区二区| 黄色欧美在线| 国产日韩亚洲| 男人的天堂久久精品| 国产aa精品| 欧美亚洲自偷自偷| 久久福利影视| 国产91一区| 开心激情综合| 国产精品亚洲四区在线观看| 免费观看久久久4p| 亚洲精品午夜av福利久久蜜桃| 久久97久久97精品免视看秋霞| 日韩**一区毛片| 亚洲字幕久久| 在线看片一区| 国产精品毛片| 午夜国产精品视频| 亚洲高清不卡| 国产+成+人+亚洲欧洲在线| 亚洲理论在线| 日韩在线播放一区二区| 99香蕉国产精品偷在线观看| 亚洲国产综合在线看不卡| 欧洲亚洲一区二区三区| 日韩中文首页| 久久中文视频| 欧美成人午夜| 最新亚洲国产| 日本麻豆一区二区三区视频| 日本不卡视频一二三区| 日韩中文字幕在线一区| 偷拍亚洲精品| 国产精品流白浆在线观看| 日韩精品欧美精品| 欧美视频一区| 精品久久一区| 九九综合在线| 亚洲精品三级| 你懂的国产精品永久在线| 成人影视亚洲图片在线| 久久久久国产精品一区三寸| 亚洲欧美日韩高清在线| 中文字幕一区二区三区在线视频| 蜜臀av亚洲一区中文字幕| 国产午夜久久av| 久久69成人| 亚洲大全视频| 欧美日本三区| 国产精品成久久久久| 1000部精品久久久久久久久| 日韩中文字幕一区二区三区| 国产亚洲精品美女久久久久久久久久| 捆绑调教美女网站视频一区| 今天的高清视频免费播放成人| 日韩欧美2区| 久久久精品网| 国产毛片精品久久| 欧美日韩国产高清| 欧美黑人巨大videos精品| 亚洲韩日在线| 国产精品尤物| 尤物网精品视频| 国内不卡的一区二区三区中文字幕| 青青青免费在线视频| 亚洲美女久久| 亚洲午夜精品久久久久久app| 国产日韩欧美一区二区三区 | 在线亚洲欧美| 国产精品国产一区| 日韩一二三区在线观看| 久久精品青草| 精品国产乱码久久久久久樱花| japanese国产精品| 成人免费一区| 日欧美一区二区| 狠狠色综合网| 91精品蜜臀一区二区三区在线| 国产精品99精品一区二区三区∴| 99热精品在线观看| 婷婷成人综合| 日韩精品诱惑一区?区三区| 国产精品免费大片| 欧美综合社区国产| 人人精品久久| 日本不卡视频一二三区| 视频一区欧美精品| 欧美日韩国产亚洲一区| av亚洲一区二区三区| 亚洲精品在线影院| 日韩欧美视频专区| 成人羞羞视频播放网站| 97在线精品| 99精品综合|