BucketLi
every thing come from new!
BlogJava
首頁
新隨筆
聯(lián)系
聚合
管理
隨筆-7 評論-23 文章-0 trackbacks-0
JAVA并發(fā)容器代碼隨讀
1.
java.util.concurrent
所提供的并發(fā)容器
java.util.concurrent
提供了多種并發(fā)容器,總體上來說有
4
類,隊列類型的
BlockingQueue
和
ConcurrentLinkedQueue,Map
類型的
ConcurrentMap,Set
類型的
ConcurrentSkipListSet
和
CopyOnWriteArraySet,List
類型的
CopyOnWriteArrayList.
這些并發(fā)容器都采用了多種手段控制并發(fā)的存取操作,并且盡可能減小控制并發(fā)所帶來的性能損耗。接下來我們會對每一種類型的實現(xiàn)類進(jìn)行代碼分析,進(jìn)而得到
java.util.con current
包所提供的并發(fā)容器在傳統(tǒng)容器上所做的工作。
2.
BlockingQueue
BlockingQueue
接口定義的所有方法實現(xiàn)都是線程安全的,它的實現(xiàn)類里面都會用鎖和其他控制并發(fā)的手段保證這種線程安全,但是這些類同時也實現(xiàn)了
Collection
接口
(
主要是
AbstractQueue
實現(xiàn)
)
,所以會出現(xiàn)
BlockingQueue
的實現(xiàn)類也能同時使用
Conllection
接口方法,而這時會出現(xiàn)的問題就是像
addAll,containsAll,retainAll
和
removeAll
這類批量方法的實現(xiàn)不保證線程安全,舉個例子就是
addAll 10
個
items
到一個
ArrayBlockingQueue,
可能中途失敗但是卻有幾個
item
已經(jīng)被放進(jìn)這個隊列里面了。
下面我們根據(jù)這幅類圖來逐個解析不同實現(xiàn)類的特性和特性實現(xiàn)代碼
DelayQueue
提供了一個只返回超時元素的阻塞隊列,也就是說,即使隊列中已經(jīng)有數(shù)據(jù)了,但是
poll
或者
take
的時候還要判定這個
element
有沒達(dá)到規(guī)定的超時時間
,poll
方法在
element
還沒達(dá)到規(guī)定的超時時間返回
null,take
則會通過
condition.waitNanos()
進(jìn)入等待狀態(tài)。一般存儲的
element
類型為
Delayed
,這個接口
JDK
中實現(xiàn)的類
有
ScheduledFutureTask,
而
DelayQueue
為
DelayedWorkQueue
的
Task
容器,后者是
ScheduledThreadPoolExecutor
的工作隊列,所以
DelayQueue
所具有的超時提供元素和線程安全特性對于并發(fā)的定時任務(wù)有很大的意義。
public
E take()
throws
InterruptedException {
final
ReentrantLock lock
=
this
.lock;
//控制并發(fā)
lock.lockInterruptibly();
try
{
for
(;;) {
E first
=
q.peek();
if
(first
==
null
) {
//condition協(xié)調(diào)隊列里面元素
available.await();
}
else
{
long
delay
=
first.getDelay(TimeUnit.NANOSECONDS);
if
(delay
>
0
) {
//因為first在隊列里面的delay最短的(優(yōu)先隊列保證),所以wait這個時間那么隊列中最短delay的元素就超時了.即
//隊列有元素供應(yīng)了.
long
tl
=
available.awaitNanos(delay);
}
else
{
E x
=
q.poll();
assert
x
!=
null
;
if
(q.size()
!=
0
)
available.signalAll();
//
wake up other takers
return
x;
}
}
}
}
finally
{
lock.unlock();
}
}
DelayQueue
的內(nèi)部數(shù)據(jù)結(jié)構(gòu)是
PriorityQueue,
因為
Delayed
接口同時繼承了
Comparable
接口,并且
Delayed
的實現(xiàn)類對于這個
compareTo
方法的實現(xiàn)是基于超時時間進(jìn)行大小比較,所以
DelayQueue
無需關(guān)心數(shù)據(jù)的排序問題,只需要做好存取的并發(fā)控制(
ReetranLock
)和超時判定即可。另外,
DelayQueue
有一個實現(xiàn)細(xì)節(jié)就是通過一個
Condition
來協(xié)調(diào)隊列中是否有數(shù)據(jù)可以提供,這對于
take
和帶有提取超時時間的
poll
是有意義的(生產(chǎn)者,消費(fèi)者的實現(xiàn))。
PriorityBlockingQueue
實現(xiàn)對于外部而言是按照元素的某種順序返回元素,同時對存取提供并發(fā)保護(hù)
(ReetranLock),
使用
Condition
協(xié)調(diào)隊列是否有新元素提供。
PriorityBlocking Queue
內(nèi)部的數(shù)據(jù)結(jié)構(gòu)為
PriorityQueue,
優(yōu)先級排序工作交給
PriorityQueue
,至于怎么排序,需要根據(jù)插入元素的
Comparable
的接口實現(xiàn),和
DelayQueue
比起來,它沒有限定死插入數(shù)據(jù)的
Comparable
實現(xiàn),而
DelayQueue
的元素實現(xiàn)
Comparable
必須按照超時時間的長短進(jìn)行比較,否則
DelayQueue
返回的元素就很可能是錯誤的。
ArrayBlockingQueue
是一個先入先出的隊列,內(nèi)部數(shù)據(jù)結(jié)構(gòu)為一個數(shù)組,并且一旦創(chuàng)建這個隊列的長度是不可改變的,當(dāng)然
put
數(shù)據(jù)時,這個隊列也不會自動增長。
ArrayBlockingQueue
也是使用
ReetranLock
來保證存取的原子性,不過使用了
notEmpty
和
notFull
兩個
Condition
來協(xié)調(diào)隊列為空和隊列為滿的狀態(tài)轉(zhuǎn)換,插入數(shù)據(jù)的時候,判定當(dāng)前內(nèi)部數(shù)據(jù)結(jié)構(gòu)數(shù)組
E[] items
的長度是否等于元素計數(shù),如果相等,說明隊列滿,
notFull.await()
,直到
items
數(shù)組重新不為滿(
removeAt,poll
等),插入數(shù)據(jù)后
notEmpty.sinal()
通知所有取數(shù)據(jù)或者移除數(shù)據(jù)并且因為
items
為空而等待的線程可以繼續(xù)進(jìn)行操作了。提取數(shù)據(jù)或者移除數(shù)據(jù)的過程剛好相反。
ArrayBlockingQueue使用三個數(shù)字來維護(hù)隊列里面的數(shù)據(jù)變更,包括takeIndex,putIndex,count,這里需要講一下takeIndex和putIndex,其中takeIndex指向下一個能夠被提取的元素,而putIndex指向下一個能夠插入數(shù)據(jù)的位置,實現(xiàn)類似下圖的結(jié)構(gòu),當(dāng)takeIndex移到內(nèi)部數(shù)組items最大長度時,重新賦值為0,也就是回到數(shù)組頭部,putIndex也是相同的策略.
/**
* 循環(huán)增加putIndex和takeIndex,如果到數(shù)組尾部,那么
* 置為0
*/
final
int
inc(
int
i)
{
return
(
++
i
==
items.length)
?
0
: i;
}
/
/**
* 插入一個item,需要執(zhí)行線程獲得了鎖
*/
private
void
insert(E x)
{
items[putIndex]
=
x;
//
累加putIndex,可能到數(shù)組尾部,那么重新指向0位置
putIndex
=
inc(putIndex);
++
count;
//put后,使用Condition通知正在等待take的線程可以做提取操作
notEmpty.signal();
}
/**
* 獲取一個元素,執(zhí)行這個操作的前提是線程已經(jīng)獲得鎖,
* 內(nèi)部調(diào)用
*/
private
E extract()
{
final
E[] items
=
this
.items;
E x
=
items[takeIndex];
items[takeIndex]
=
null
;
//
累加takeIndex,有可能到數(shù)組尾部,重新調(diào)到數(shù)組頭部
takeIndex
=
inc(takeIndex);
--
count;
//
take后,使用Condition通知正在等待插入的線程可以插入
notFull.signal();
return
x;
}
這里需要解釋下
Condition
的實現(xiàn),
Condition
現(xiàn)在的
JDK
實現(xiàn)只有
AQS
的
ConditionObject
,并且通過
ReetranLock
的
newConditon()
方法暴露出來,這是因為
Condition
的
await()
或者
sinal()
一般在
lock.lock()
與
lock.unlock()
之間執(zhí)行,當(dāng)執(zhí)行
condition.await()
方法時,它會首先釋放掉本線程持有的鎖,然后自己進(jìn)入等待隊列,直到
sinal(),
喚醒后又會重新去試圖拿到鎖
,
拿到后執(zhí)行
await
下方的代碼
,
其中釋放當(dāng)前鎖和得到當(dāng)前鎖都需要
ReetranLock
的
tryAcquire
(
int args
)方法來判定,并且享受
ReetranLock
的重進(jìn)入特性。
public
final
void
await()
throws
InterruptedException
{
if
(Thread.interrupted())
throw
new
InterruptedException();
//
加一個新的condition等待節(jié)點(diǎn)
Node node
=
addConditionWaiter();
//
釋放自己占用的鎖
int
savedState
=
fullyRelease(node);
int
interruptMode
=
0
;
while
(
!
isOnSyncQueue(node))
{
//
如果當(dāng)前線程等待狀態(tài)是CONDITION,park住當(dāng)前線程,等待condition的signal來解除
LockSupport.park(
this
);
if
((interruptMode
=
checkInterruptWhileWaiting(node))
!=
0
)
break
;
}
if
(acquireQueued(node, savedState)
&&
interruptMode
!=
THROW_IE)
interruptMode
=
REINTERRUPT;
if
(node.nextWaiter
!=
null
)
unlinkCancelledWaiters();
if
(interruptMode
!=
0
)
reportInterruptAfterWait(interruptMode);
}
LinkedBlockingQueue
是一個鏈表結(jié)構(gòu)構(gòu)成的隊列,并且節(jié)點(diǎn)是單向的,也就是只有
next,
沒有
prev,
可以設(shè)置容量,如果不設(shè)置,最大容量為
Integer.MAX_VALUE
,隊列只持有頭結(jié)點(diǎn)和尾節(jié)點(diǎn)以及元素數(shù)量,通過
putLock
和
takeLock
兩個
ReetranLock
分別控制存和取的并發(fā)
,
但是
remove,toArray,toString,clear, drainTo
以及迭代器等操作會同時取得
putLock
和
takeLock
,并且同時
lock,
此時存或者取操作都會不可進(jìn)行,這里有個細(xì)節(jié)需要注意的就是所有需要同時
lock
的地方順序都是先
putLock.lock
再
takeLock.lock
,這樣就避免了可能出現(xiàn)的死鎖問題。
takeLock
實例化出一個
notEmpty
的
Condition,putLock
實例化一個
notFull
的
Condition,
兩個
Condition
協(xié)調(diào)即時通知線程隊列滿與不滿的狀態(tài)信息,這在前面幾種
BlockingQueue
實現(xiàn)中也非常常見,在需要用到線程間通知的場景時,各位不妨參考下。另外dequeue的時候需要改變頭節(jié)點(diǎn)的引用地址,否則肯定會造成不能GC而內(nèi)存泄露
private
E dequeue()
{
Node
<
E
>
h
=
head;
Node
<
E
>
first
=
h.next;
//
將原始節(jié)點(diǎn)的next指針指向自己,這樣就能GC到自己否則虛擬機(jī)會認(rèn)為這個節(jié)點(diǎn)仍然在用而不銷毀(不知道是否理解有誤)
h.next
=
h;
//
help GC
head
=
first;
E x
=
first.item;
first.item
=
null
;
return
x;
}
BlockingDequeue
為阻塞的雙端隊列接口,繼承了
BlockingQueue
,雙端隊列的最大的特性就是能夠?qū)⒃靥砑拥疥犃心┪?/span>,
也能夠添加到隊列首部,取元素也是如此。
LinkedBlockingDequeue
實現(xiàn)了
BlockingDequeue
接口,就像
LinkedBlockingQueue
類似,也是由鏈表結(jié)構(gòu)構(gòu)成,但是和
LinkedBlockingQueue
不一樣的是,節(jié)點(diǎn)元素變成了可雙向檢索,也就是一個
Node
持有
next
節(jié)點(diǎn)引用,同時持有
prev
節(jié)點(diǎn)引用,這對隊列的頭尾數(shù)據(jù)存取是有決定性意義的。
LinkedBlockingDequeue
只采用了一個
ReetranLock
來控制存取并發(fā),并且由這個
lock
實例化了
2
個
Condition notEmpty
和
notFull,count變量維護(hù)隊列長度,這里只使用一個lock來維護(hù)隊列的讀寫并發(fā),個人理解是頭尾的讀寫如果使用頭尾分開的2個鎖,在維護(hù)隊列長度和隊列Empty/Full狀態(tài)會帶來問題,如果使用隊列長度做為判定依據(jù)將不得不對這個變量進(jìn)行鎖定.
//
無論是offerLast,offerFirst,pollFirst,pollLast等等方法都會使用同一把鎖.
public
E pollFirst() {
final
ReentrantLock lock
=
this
.lock;
lock.lock();
try
{
return
unlinkFirst();
}
finally
{
lock.unlock();
}
}
public
E pollLast() {
final
ReentrantLock lock
=
this
.lock;
lock.lock();
try
{
return
unlinkLast();
}
finally
{
lock.unlock();
}
}
3.
ConcurrentMap
ConcurrentMap
定義了
V putIfAbsent(K key,V value),Boolean remove(Object Key,Object value),Boolean replace(K key,V oldValue,V newValue)
以及
V replace(K key,V value)
四個方法,幾個方法的特性并不難理解,
4
個方法都是線程安全的。
ConcurrentHashMap
是
ConcurrentMap
的一個實現(xiàn)類,這個類的實現(xiàn)相當(dāng)經(jīng)典,基本思想就是分拆鎖,默認(rèn)
ConcurrentHashMap
會實例化一個持有
16
個
Segment
對象的數(shù)組,Segment數(shù)組大小是可以設(shè)定的,構(gòu)造函數(shù)里的concurrencyLevel指定這個值,但是需要注意的是,這個值并不是直接賦值.Segment數(shù)組最大長度為MAX_SEGMENTS = 1 << 16
int
sshift
=
0
;
int
ssize
=
1
;
//
ssize是左移位的,也就是2,4,8,16,32
增長(*2),所以你設(shè)定concurrencyLevel為10的時候,這個時候并發(fā)數(shù)最大為8.
while
(ssize
<
concurrencyLevel)
{
++
sshift;
ssize
<<=
1
;
}
每個
Segment
維持一個自動增長的
HashEntry
數(shù)組
(
根據(jù)一個閾值確定是否要增長長度,并不是滿了才做
).
int
c
=
count;
//
threshold一般(int)(capacity * loadFactor),
if
(c
++
>
threshold)
rehash();
下面3段代碼是ConcurrentHashMap的初始化Segment,計算hash值,以及如何選擇Segment的代碼以及示例注解.
public
ConcurrentHashMap(
int
initialCapacity,
float
loadFactor,
int
concurrencyLevel)
{
if
(
!
(loadFactor
>
0
)
||
initialCapacity
<
0
||
concurrencyLevel
<=
0
)
throw
new
IllegalArgumentException();
//
首先確定segment的個數(shù),左移位,并且記錄移了幾次,比如conurrencyLevel為30,那么2->4->8->16,ssize為16,sshift為4
if
(concurrencyLevel
>
MAX_SEGMENTS)
concurrencyLevel
=
MAX_SEGMENTS;
int
sshift
=
0
;
int
ssize
=
1
;
while
(ssize
<
concurrencyLevel)
{
++
sshift;
ssize
<<=
1
;
}
//
segmentShift為28
segmentShift
=
32
-
sshift;
//
segmentMask為15
segmentMask
=
ssize
-
1
;
//
this.segments=new Segment[16]
this
.segments
=
Segment.newArray(ssize);
if
(initialCapacity
>
MAXIMUM_CAPACITY)
initialCapacity
=
MAXIMUM_CAPACITY;
//
假設(shè)initialCapacity使用32,那么c=2
int
c
=
initialCapacity
/
ssize;
if
(c
*
ssize
<
initialCapacity)
++
c;
int
cap
=
1
;
//
cap為2
while
(cap
<
c)
cap
<<=
1
;
//
每個Segment的容量為2
for
(
int
i
=
0
; i
<
this
.segments.length;
++
i)
this
.segments[i]
=
new
Segment
<
K,V
>
(cap, loadFactor);
}
/** */
/**
*segmentShift為28,segmentMask為15(1111)
*因為hash值為int,所以32位的
*hash >>> segentShift會留下最高的4位,
*再與mask 1111做&操作
*所以這個最終會產(chǎn)生 0-15的序列.
*/
final
Segment
<
K,V
>
segmentFor(
int
hash)
{
return
segments[(hash
>>>
segmentShift)
&
segmentMask];
}
/**
*將計算的hash值補(bǔ)充到原始hashCode中,這是為了防止
*外部用戶傳進(jìn)來劣質(zhì)的hash值(比如重復(fù)度很高)所帶來
*的危害.
*/
private
static
int
hash(
int
h)
{
//
Spread bits to regularize both segment and index locations,
//
using variant of single-word Wang/Jenkins hash.
h
+=
(h
<<
15
)
^
0xffffcd7d
;
h
^=
(h
>>>
10
);
h
+=
(h
<<
3
);
h
^=
(h
>>>
6
);
h
+=
(h
<<
2
)
+
(h
<<
14
);
return
h
^
(h
>>>
16
);
}
當(dāng)
put
進(jìn)來一個
key
、
value
對,
ConcurrentHashMap
會計算
Key
的
hash
值,然后從
Segment
數(shù)組根據(jù)
key
的
Hash
值選出一個
Segment
,調(diào)用其
put
方法,
Segment
級別的
put
方法通過
ReetranLock
來控制讀取的并發(fā),其實
Segment
本身繼承了
ReetranLock
類。
Segment
的
put
方法在
lock()
后,首先對數(shù)組長度加了新的元素之后是否會超過閾值
threshold
進(jìn)行了判定,如果超過,那么進(jìn)行
rehash()
,
rehash()
的過程相對繁瑣,首先數(shù)組會自動增長一倍,然后需要對
HashEntry
數(shù)組中的所有元素都需要重新計算
hash
值,并且置到新數(shù)組的新的位置,同時為了減小操作損耗,將原來不需要移動的數(shù)據(jù)不做移動操作(power-of-two expansion,在默認(rèn)threshold,在數(shù)組擴(kuò)大一倍時只需要移動1/6元素,其他都可以不動)。所有動作完成之后,通過一個
while
循環(huán)尋找
Segment
中是否有相同
Key
存在,如果已經(jīng)存在,那么根據(jù)
onlyIfAbsent
參數(shù)確定是否替換
(
如果為
true,
不替換,如果為
false,
替換掉
value),
然后返回替換的
value,
如果不存在,那么新生成一個
HashEntry,
并且根據(jù)一開始計算出來的
index
放到數(shù)組指定位置,并且累積元素計數(shù)
,返回
put
的值。最后
unlock()
釋放掉鎖.
4.
CopyOnWriteArrayList
和
CopyOnWriteArraySet
CopyOnWriteList
是線程安全的
List
實現(xiàn),其底層數(shù)據(jù)存儲結(jié)構(gòu)為數(shù)組
(Object[] array),
它在讀操作遠(yuǎn)遠(yuǎn)多于寫操作的場景下表現(xiàn)良好,這其中的原因在于其讀操作
(get(),indexOf(),isEmpty(),contains())
不加任何鎖,而寫操作
(set(),add(),remove())
通過
Arrays.copyOf()
操作拷貝當(dāng)前底層數(shù)據(jù)結(jié)構(gòu)
(array)
,在其上面做完增刪改等操作,再將新的數(shù)組置為底層數(shù)據(jù)結(jié)構(gòu),同時為了避免并發(fā)增刪改,
CopyOnWriteList
在這些寫操作上通過一個
ReetranLock
進(jìn)行并發(fā)控制。另外需要注意的是,
CopyOnWriteList
所實現(xiàn)的迭代器其數(shù)據(jù)也是底層數(shù)組鏡像,所以在
CopyOnWriteList
進(jìn)行
interator
,同時并發(fā)增刪改
CopyOnWriteList
里的數(shù)據(jù)實不會拋“
ConcurrentModificationException
”,當(dāng)然在迭代器上做
remove,add,set
也是無效的(拋
UnsupportedOperationExcetion
),因為迭代器上的數(shù)據(jù)只是當(dāng)前
List
的數(shù)據(jù)數(shù)組的一個拷貝而已。
CopyOnWriteSet
是一個線程安全的
Set
實現(xiàn),然后持有一個
CopyOnWriteList
實例
,
其所有的操作都是這個
CopyOnWriteList
實例來實現(xiàn)的。
CopyOnWriteSet
與
CopyOnWriteList
的區(qū)別實際上就是
Set
與
List
的區(qū)別,前者不允許有重復(fù)的元素,后者是可以的,所以
CopyOnWriteSet
的
add
和
addAll
兩個操作使用的是其內(nèi)部
CopyOnWriteList
實例的
addAbsent()
和
addAllAbsent()
兩個防止重復(fù)元素的方法,
addAbsent()
實現(xiàn)是拷貝底層數(shù)據(jù)數(shù)組,然后逐一比較是否相同,如果有一個相同,那么直接返回
false,
說明插入失敗,如果和其他元素不同,那么將元素加入到新的數(shù)組中,最后置回新的數(shù)組
, addAllAbsent()
方法實現(xiàn)則是能有多少數(shù)據(jù)插入就插入,也就是說
addAllAbsent
一個集合的數(shù)據(jù),可能只有一部分插入成功,另外一部分因為元素相同而遭丟棄,完成后返回插入的元素。
posted on 2010-11-25 13:43
BucketLI
閱讀(5178)
評論(3)
編輯
收藏
評論:
#
re: JAVA并發(fā)容器代碼隨讀[未登錄] 2010-11-25 16:12 |
xylz
不錯,很有見地
回復(fù)
更多評論
#
re: JAVA并發(fā)容器代碼隨讀 2010-11-26 18:13 |
BucketLI
@xylz
呵呵,新手一個,還有很多不理解和理解錯的地方.
回復(fù)
更多評論
#
re: JAVA并發(fā)容器代碼隨讀
2012-10-30 10:53 |
mengmeng.zhangmm
支持一下
回復(fù)
更多評論
新用戶注冊
刷新評論列表
只有注冊用戶
登錄
后才能發(fā)表評論。
網(wǎng)站導(dǎo)航:
博客園
IT新聞
Chat2DB
C++博客
博問
管理
<
2010年11月
>
日
一
二
三
四
五
六
31
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
1
2
3
4
5
6
7
8
9
10
11
常用鏈接
我的隨筆
我的評論
我的參與
最新評論
留言簿
(1)
給我留言
查看公開留言
查看私人留言
隨筆檔案
2011年2月 (1)
2010年12月 (2)
2010年11月 (1)
2010年10月 (1)
2010年9月 (2)
搜索
最新評論
1.?re: zookeeper使用和原理探究(一)
寫得很好,希望繼續(xù)寫下去,學(xué)習(xí)了
--shao_win
2.?re: zookeeper使用和原理探究(一)
評論內(nèi)容較長,點(diǎn)擊標(biāo)題查看
--zookeeper
3.?re: zookeeper使用和原理探究(一)
評論內(nèi)容較長,點(diǎn)擊標(biāo)題查看
--zookeeper
4.?re: zookeeper使用和原理探究(一)
評論內(nèi)容較長,點(diǎn)擊標(biāo)題查看
--qingqing
5.?re: zookeeper使用和原理探究(一)
贊一個
--Relieved
閱讀排行榜
1.?zookeeper使用和原理探究(一)(118587)
2.?JAVA LOCK代碼淺析(13163)
3.?JAVA并發(fā)容器代碼隨讀(5178)
4.?JAVA線程池代碼淺析(5076)
5.?Log4j代碼隨讀(4305)
評論排行榜
1.?zookeeper使用和原理探究(一)(15)
2.?JAVA并發(fā)容器代碼隨讀(3)
3.?JAVA LOCK代碼淺析(2)
4.?Netty代碼分析(1)
5.?Google Megastore初探(1)
Powered by:
博客園
模板提供:
滬江博客
Copyright ©2025 BucketLI
主站蜘蛛池模板:
美女一级毛片免费观看
|
亚洲久本草在线中文字幕
|
国产最新凸凹视频免费
|
一二三四视频在线观看中文版免费
|
久久精品国产免费观看
|
四虎在线最新永久免费
|
综合在线免费视频
|
99视频在线精品免费观看6
|
毛片免费在线观看网站
|
最近高清国语中文在线观看免费
|
毛片免费在线观看网站
|
国产午夜无码视频免费网站
|
国产精品免费视频播放器
|
亚洲国产高清精品线久久
|
亚洲综合伊人久久大杳蕉
|
亚洲gv猛男gv无码男同短文
|
日木av无码专区亚洲av毛片
|
亚洲国产精品免费在线观看
|
亚洲色精品VR一区区三区
|
亚洲AV日韩AV无码污污网站
|
污污视频网站免费观看
|
中文无码日韩欧免费视频
|
无码国产精品一区二区免费模式
|
亚洲人成网站日本片
|
亚洲kkk4444在线观看
|
特级做a爰片毛片免费看
|
青柠影视在线观看免费高清
|
亚洲AV无码成人精品区狼人影院
|
羞羞视频免费观看
|
中国黄色免费网站
|
91成人免费观看
|
在线免费观看一级片
|
亚洲精品高清在线
|
亚洲综合国产精品
|
亚洲美国产亚洲AV
|
久久久受www免费人成
|
97免费人妻在线视频
|
国内外成人免费视频
|
亚洲一区日韩高清中文字幕亚洲
|
亚洲AV无码成人精品区蜜桃
|
亚洲综合丁香婷婷六月香
|