亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 開發 > Java > 正文

基于Java 生產者消費者模式(詳細分析)

2024-07-13 10:16:04
字體:
來源:轉載
供稿:網友

生產者消費者模式是多線程中最為常見的模式:生產者線程(一個或多個)生成面包放進籃子里(集合或數組),同時,消費者線程(一個或多個)從籃子里(集合或數組)取出面包消耗。雖然它們任務不同,但處理的資源是相同的,這體現的是一種線程間通信方式。

本文將先說明單生產者單消費者的情況,之后再說明多生產者多消費者模式的情況。還會分別使用wait()/nofity()/nofityAll()機制、lock()/unlock()機制實現這兩種模式。

在開始介紹模式之前,先解釋下wait()、notify()和notifyAll()方法的用法細節以及改進的lock()/unlock()、await()/signal()/signalAll()的用法。

1.等待、喚醒機制的原理

wait()、notify()和notifyAll()分別表示讓線程進入睡眠、喚醒睡眠線程以及喚醒所有睡眠的線程。但是,對象是哪個線程呢?另外,在API文檔中描述這三個方法都必須在有效監視器(可理解為持有鎖)的前提下使用。這三個方法和鎖有什么關系呢?

以同步代碼塊synchronized(obj){}或同步函數為例,在它們的代碼結構中可以使用wait()、notify()以及notifyAll(),因為它們都持有鎖。

對于下面的兩個同步代碼塊來說,分別使用的是鎖obj1和鎖obj2,其中線程1、線程2執行的是obj1對應的同步代碼,線程3、線程4執行的是obj2對應的同步代碼。

class MyLock implements Runnable { public int flag = 0; Object obj1 = new Object(); Object obj2 = new Object(); public void run(){ while(true){  if(flag%2=0){  synchronized(obj1){ //線程t1和t2執行此同步任務   //try{obj1.wait();}catch(InterruptedException i){}   //obj1.notify()   //obj1.notifyAll()  }  } else {  synchronized(obj2){ //線程t3和t4執行此同步任務   //try{obj2.wait();}catch(InterruptedException i){}   //obj2.notify()   //obj2.notifyAll()  }  } } }}class Demo { public static void main(String[] args){ MyLock ml = new MyLock(); Thread t1 = new Thread(ml); Thread t2 = new Thread(ml); Thread t3 = new Thread(ml); Thread t4 = new Thread(ml); t1.start(); t2.start(); try{Thread.sleep(1)}catch(InterruptedException i){}; ml.flag++; t3.start(); t4.start(); }}

當t1開始執行到wait()時,它將進入睡眠狀態,但卻不是一般的睡眠,而是在一個被obj1標識的線程池中睡眠(實際上是監視器對應線程池,只不過此時的監視器和鎖是綁定在一起的)。當t2開始執行,它發現鎖obj1被其他線程持有,它將進入睡眠態,這次睡眠是因為鎖資源等待而非wait()進入的睡眠。因為t2已經判斷過它要申請的是obj1鎖,因此它也會進入obj1這個線程池睡眠,而不是普通的睡眠。同理t3和t4,這兩個線程會進入obj2線程池睡眠。

當某個線程執行到notify()時,這個notify()將 隨機 喚醒它 所屬鎖對應線程池 中的 任意一個 線程。例如,obj1.notify()將喚醒obj1線程池中任意一個睡眠的線程(當然,如果沒有睡眠線程則什么也不做)。同理notifyAll()則是喚醒所屬鎖對應線程池中所有睡眠的線程。

必須要搞清楚的是"對應鎖",因為在調用wait()、notify()和notifyAll()時都必須明確指定鎖。例如,obj1.wait()。如果省略了所屬鎖,則表示的是this這個對象,也就是說,只有在非靜態的同步函數中才能省略這三個方法的前綴。

簡而言之,當使用了同步,就使用了鎖,線程也就有了歸屬,它的所有依據都由所屬鎖來決定。例如,線程同步時,判斷鎖是否空閑以決定是否執行后面的代碼,亦決定是否去特定的線程池中睡眠,當喚醒時也只會喚醒所屬鎖對應線程池中的線程。

這幾個方法在應用上,一般在一次任務中,wait()和notify()/notifyAll()是成對出現且擇一執行的。換句話說,就是這一輪原子性同步執行過程中,要么執行wait()進入睡眠,要么執行notify()喚醒線程池中的睡眠線程。要如何實現擇一執行,可以考慮使用標記的方式來作為判斷依據。參考后文的例子。

2.Lock和Condition

wait()系列的三個方法局限性很大,因為無論是睡眠還是喚醒的動作,都完全和鎖耦合在一起了。例如,鎖obj1關聯的線程只能喚醒obj1線程池中的線程,而無法喚醒鎖obj2關聯的線程;再例如,在原來synchronized同步時,鎖是在開始同步時隱式地自動獲取的,且是在執行完一整個任務后,又隱式地自動釋放鎖,也就是說獲取鎖和釋放鎖的動作無法人為控制。

從JDK 1.5開始,java提供了java.util.concurrent.locks包,這個包中提供了Lock接口、Condition接口和ReadWriteLock接口,前兩個接口將鎖和監視器方法(睡眠、喚醒操作)解耦了。其中Lock接口只提供鎖,通過鎖方法newConditon()可以生成一個或多個與該鎖關聯的監視器,每個監視器都有自己的睡眠、喚醒方法。也就是說Lock替代了synchronized方法和同步代碼塊的使用,Condition替代了Object監視器方法的使用。

如下圖:

Java,生產者,消費者

當某線程執行condition1.await()時,該線程將進入condition1監視器對應的線程池睡眠,當執行condition1.signal()時,將隨機喚醒condition1線程池中的任意一個線程,當執行condition1.signalAll()時,將喚醒condition1線程池中的所有線程。同理,對于condition2監視器也是一樣的。

即使有多個監視器,但只要它們關聯的是同一個鎖對象,就可以跨監視器操作對方線程。例如condition1中的線程可以執行condition2.signal()來喚醒condition2線程池中的某個線程。

要使用這種鎖、監視器的關聯方式,參考如下步驟:

import java.util.concurrent.locks.*;Lock l = new ReentrantLock();Condition con1 = l.newCondition();condition con2 = l.newCondition();l.lock();try{ //包含await()、signal()或signalAll()的代碼段...} finally { l.unlock(); //由于代碼段可能異常,但unlock()是必須執行的,所以必須使用try,且將unlock()放進finally段}

具體用法見后文關于Lock、condition的示例代碼。

3.單生產者單消費者模式

一個生產者線程,一個消費者線程,生產者每生產一個面包放進盤子里,消費者從盤子里取出面包進行消費。其中生產者判斷是否繼續生產的依據是盤子里沒有面包,而消費者判斷是否消費的依據是盤子里有面包。由于這個模式中,盤子一直只放一個面包,因此可以把盤子省略掉,生產者和消費者直接手把手地交遞面包即可。

首先需要描述這三個類,一是多線程共同操作的資源(此處即面包),二是生產者,三是消費者。在下面的例子中,我把生產面包和消費面包的方法分別封裝到了生產者和消費者類中,如果把它們封裝在面包類中則更容易理解。

//描述資源:面包的名稱和編號,由編號決定面包的號碼class Bread { public String name; public int count = 1; public boolean flag = false; //該標記為wait()和notify()提供判斷標記}//生產者和消費者先后處理的面包資源是同一個,要確保這一點,//可以按單例模式來設計面包類,也可以將同一個面包對象通過構造方法傳遞給生產者和消費者,此處使用后一種方式。//描述生產者class Producer implements Runnable { private Bread b; //生產者的成員:它要處理的資源 Producer(Bread b){ this.b = b; } //提供生產面包的方法 public void produce(String name){ b.name = name + b.count; b.count++; } public void run(){ while(true){  synchronized(Bread.class){ //使用Bread.class作為鎖標識,使得生產者和消費者的同步代碼塊可以使用同一個鎖  if(b.flag){  //wait()必須在同步代碼塊內部,不僅因為必須持有鎖才能睡眠,而且對鎖這個資源的判斷會出現混亂   try{Bread.class.wait();}catch(InterruptedException i){}  }  produce("面包");  System.out.println(Thread.currentThread().getName()+"----生產者------"+b.name);  try{Thread.sleep(10);}catch(InterruptedException i){}  b.flag = true;  //標記的切換也必須在保持同步  Bread.class.notify(); //notify()也必須同步,否則鎖都已經釋放了,就無法做喚醒動作  //ps:一次同步任務中,wait()和notify()應當只能其中一個執行,否則對方線程會混亂  } } }}//描述消費者class Consumer implements Runnable { private Bread b; //消費者的成員:它要處理的資源 Consumer(Bread b){ this.b = b; } //提供消費面包的方法 public String consume(){ return b.name; } public void run(){ while(true){  synchronized(Bread.class){  if(!b.flag){   try{Bread.class.wait();}catch(InterruptedException i){}  }  System.out.println(Thread.currentThread().getName()+"----消費者-------------"+consume());  try{Thread.sleep(10);}catch(InterruptedException i){}  b.flag = false;  Bread.class.notify();  } } }}public class ProduceConsume_1{ public static void main(String[] args) { //1.創建資源對象 Bread b = new Bread(); //2.創建生產者和消費者對象,將同一個面包對象傳遞給生產者和消費者 Producer pro = new Producer(b); Consumer con = new Consumer(b); //3.創建線程對象 Thread pro_t = new Thread(pro); Thread con_t = new Thread(con); pro_t.start(); con_t.start(); }}

最后的執行結果應當生產一個、消費一個,如此不斷循環。如下:

Thread-0----生產者------面包1Thread-1----消費者-------------面包1Thread-0----生產者------面包2Thread-1----消費者-------------面包2Thread-0----生產者------面包3Thread-1----消費者-------------面包3Thread-0----生產者------面包4Thread-1----消費者-------------面包4Thread-0----生產者------面包5Thread-1----消費者-------------面包5Thread-0----生產者------面包6Thread-1----消費者-------------面包6

4.使用Lock和Condition實現單生產單消費模式

代碼如下:

import java.util.concurrent.locks.*;class Bread { public String name; public int count = 1; public boolean flag = false; //為生產者和消費者提供同一個鎖對象以及同一個Condition對象 public static Lock lock = new ReentrantLock(); public static Condition condition = lock.newCondition();}class Producer implements Runnable { private Bread b; Producer(Bread b){ this.b = b; } public void produce(String name){ b.name = name + b.count; b.count++; } public void run(){ while(true){  //使用Bread.lock來鎖住資源  Bread.lock.lock();   try{  if(b.flag){   try{Bread.condition.await();}catch(InterruptedException i){}  }  produce("面包");  System.out.println(Thread.currentThread().getName()+"----生產者------"+b.name);  try{Thread.sleep(10);}catch(InterruptedException i){}  b.flag = true;   Bread.condition.signal();   } finally {  Bread.lock.unlock();  } } }}class Consumer implements Runnable { private Bread b;  Consumer(Bread b){ this.b = b; } public String consume(){ return b.name; } public void run(){ while(true){  //使用Bread.lock來鎖住資源  Bread.lock.lock();  try{  if(!b.flag){   try{Bread.condition.await();}catch(InterruptedException i){}  }  System.out.println(Thread.currentThread().getName()+"----消費者-------------"+consume());  try{Thread.sleep(10);}catch(InterruptedException i){}  b.flag = false;  Bread.condition.signal();  } finally {  Bread.lock.unlock();  } } }}public class ProduceConsume_1{ public static void main(String[] args) { //1.創建資源對象 Bread b = new Bread(); //2.創建生產者和消費者對象,將同一個面包對象傳遞給生產者和消費者 Producer pro = new Producer(b); Consumer con = new Consumer(b); //3.創建線程對象 Thread pro_t = new Thread(pro); Thread con_t = new Thread(con); pro_t.start(); con_t.start(); }}

5.多生產多消費模式(單面包)

這里先說明多生產者多消費者,但同一個時刻最多只能有一個面包的模式,這個模式在實際中可能是不理想的,但為了引出后面真實的多生產多消費模式,我覺得有必要在這里解釋這種模式,并且分析這種模式以及如何從單生產單消費的代碼演變而來。

如下圖:

Java,生產者,消費者

從單生產單消費到多生產多消費,因為多線程安全問題和死鎖問題,所以有兩個方面的問題需要考慮:

對于某一方來說,如何讓多線程達到和單線程同樣的生產或消費能力?也就是說,如何讓多線程看上去就是單線程。多線程和單線程最大的區別在于多線程安全問題,因此,只要保證多線程執行的任務能夠同步即可。

第1個問題考慮的是某一方多線程的問題,第2個問題考慮的是兩方如何能和諧配合完成生產消費問題。也就是如何保證生產方和消費方一方活動的同時另一方睡眠。只需在某一方執行完同步任務時,喚醒另一方即可。

其實從單線程到多線程,就兩個問題需要考慮:不同步和死鎖。(1)當生產方和消費方都出現了多線程,可以將生產方的多線程看成一個線程整體、消費方的多線程也看成一個整體,這解決的是線程安全問題。(2)再將生產方整體和消費方整體兩方結合起來看成多線程,來解決死鎖問題,而java中解決死鎖的方式就是喚醒對方或喚醒所有。

問題是如何保證某一方的多線程之間同步?以多線程執行單消費方的代碼為例進行分析。

while(true){ synchronized(Bread.class){ if(!b.flag){  try{Bread.class.wait();}catch(InterruptedException i){} } System.out.println(Thread.currentThread().getName()+"----消費者-------------"+consume()); try{Thread.sleep(10);}catch(InterruptedException i){} b.flag = false; Bread.class.notify(); }}

假設消費線程1消費完一個面包后喚醒了消費線程2,并繼續循環,判斷if(!flag),它將wait,于是鎖被釋放。假設CPU正好選中了消費線程2,那么消費線程2也將進入wait。當生產方生產了一個面包后,假設喚醒了消費線程1,它將從wait語句處繼續向下消費剛生產完的面包,假設正好再次喚醒了消費線程2,當消費線程2被CPU選中后,消費線程2也將從wait語句處向下消費,消費的也是剛才生產的面包,問題再此出現了,連續喚醒的消費線程1和2消費的是同一個面包,也就是說面包被重復消費了。這又是多線程不同步問題。

說了一大段,其實將視線放大后分析就很簡單了,只要某一方的2個或多個線程都因為判斷b.flag而wait,那么這兩個或多個線程有可能會被連續喚醒而繼續向下生產或消費。這造成了多線程不同步問題。

不安全的問題就出在同一方的多個線程在連續喚醒后繼續向下生產或消費。這是if語句引起的,如果能夠讓wait的線程在喚醒后還回頭判斷b.flag是否為true,就能讓其決定是否繼續wait還是向下生產或消費。

可以將if語句替換為while語句來滿足要求。這樣一來,無論某一方的多個線程是否被連續喚醒,它們都將回頭判斷b.flag。

while(true){ synchronized(Bread.class){ while(!b.flag){  try{Bread.class.wait();}catch(InterruptedException i){} } System.out.println(Thread.currentThread().getName()+"----消費者-------------"+consume()); try{Thread.sleep(10);}catch(InterruptedException i){} b.flag = false; Bread.class.notify(); }}

解決了第一個多線程安全的問題,但會出現死鎖問題。這很容易分析,將生產方看作一個整體,將消費方也看作一個整體,當生產方線程都wait了(生產方的線程被連續喚醒時會出現該方線程全部wait),消費方也都wait了,死鎖就出現了。其實放大了看,將生產方、消費方分別看作一個線程,這兩個線程組成多線程,當某一方wait后無法喚醒另一方,另一方也一定會wait,于是就死鎖了。

對于雙方死鎖的問題,只要保證能喚醒對方,而非本方連續喚醒就能解決。使用notifyAll()或signalAll()即可,也可以通過signal()喚醒對方線程解決,見下面的第二段代碼。

根據上面的分析,將單生產、單消費模式的代碼改進一下,就可以變為多生產多消費單面包模式。

//代碼段1class Bread { public String name; public int count = 1; public boolean flag = false; }//描述生產者class Producer implements Runnable { private Bread b;  Producer(Bread b){  this.b = b; } public void produce(String name){  b.name = name + b.count;  b.count++; } public void run(){  while(true){    synchronized(Bread.class){     while(b.flag){       try{Bread.class.wait();}catch(InterruptedException i){}     }     produce("面包");     System.out.println(Thread.currentThread().getName()+"----生產者------"+b.name);     try{Thread.sleep(10);}catch(InterruptedException i){}     b.flag = true;      Bread.class.notifyAll();   }  } }}//描述消費者class Consumer implements Runnable { private Bread b; Consumer(Bread b){  this.b = b; } public String consume(){  return b.name; } public void run(){  while(true){    synchronized(Bread.class){     while(!b.flag){      try{Bread.class.wait();}catch(InterruptedException i){}     }     System.out.println(Thread.currentThread().getName()+"----消費者-------------"+consume());     try{Thread.sleep(10);}catch(InterruptedException i){}     b.flag = false;     Bread.class.notifyAll();    }  } }}public class ProduceConsume_5 { public static void main(String[] args) {  //1.創建資源對象  Bread b = new Bread();  //2.創建生產者和消費者對象  Producer pro = new Producer(b);  Consumer con = new Consumer(b);  //3.創建線程對象  Thread pro_t1 = new Thread(pro); //生產線程1  Thread pro_t2 = new Thread(pro); //生產線程2  Thread con_t1 = new Thread(con); //消費線程1  Thread con_t2 = new Thread(con); //消費線程2  pro_t1.start();  pro_t2.start();  con_t1.start();  con_t2.start(); }}

以下是采用Lock和Conditon重構后的代碼,使用的是signal()喚醒對方線程的方法。

//代碼段2import java.util.concurrent.locks.*;class Bread { public String name; public int count = 1; public boolean flag = false; public static Lock lock = new ReentrantLock(); public static Condition pro_con = lock.newCondition(); public static Condition con_con = lock.newCondition();}//描述生產者class Producer implements Runnable { private Bread b; Producer(Bread b){  this.b = b; } public void produce(String name){  b.name = name + b.count;  b.count++; } public void run(){  while(true){   Bread.lock.lock();   try{    while(b.flag){     try{Bread.pro_con.await();}catch(InterruptedException i){}    }    produce("面包");    System.out.println(Thread.currentThread().getName()+"----生產者------"+b.name);    try{Thread.sleep(10);}catch(InterruptedException i){}    b.flag = true;    Bread.con_con.signal(); //喚醒的是consumer線程   } finally {    Bread.lock.unlock();   }  } }}//描述消費者class Consumer implements Runnable { private Bread b; Consumer(Bread b){  this.b = b; } public String consume(){  return b.name; } public void run(){  while(true){   Bread.lock.lock();   try{    while(!b.flag){     try{Bread.con_con.await();}catch(InterruptedException i){}    }    System.out.println(Thread.currentThread().getName()+"----消費者-------------"+consume());    try{Thread.sleep(10);}catch(InterruptedException i){}    b.flag = false;    Bread.pro_con.signal();  //喚醒的是producer線程   } finally {    Bread.lock.unlock();   }  } }}public class ProduceConsume_6 { public static void main(String[] args) {  //1.創建資源對象  Bread b = new Bread();  //2.創建生產者和消費者對象  Producer pro = new Producer(b);  Consumer con = new Consumer(b);  //3.創建線程對象  Thread pro_t1 = new Thread(pro);  Thread pro_t2 = new Thread(pro);  Thread con_t1 = new Thread(con);  Thread con_t2 = new Thread(con);  pro_t1.start();  pro_t2.start();  con_t1.start();  con_t2.start(); }}

關于多生產、多消費問題做個總結:

(1).解決某一方多線程不同步的方案是使用while(flag)來判斷是否wait;

(2).解決雙方死鎖問題的方案是喚醒對方,可以使用notifyAll(),signalAll()或對方監視器的signal()方法。

6.多生產多消費模式

有多個生產者線程,多個消費者線程,生產者將生產的面包放進籃子(集合或數組)里,消費者從籃子里取出面包。生產者判斷繼續生產的依據是籃子已經滿了,消費者判斷繼續消費的依據是籃子是否空了。此外,當消費者取出面包后,對應的位置又空了,生產者可以回頭從籃子的起始位置繼續生產,這可以通過重置籃子的指針來實現。

在這個模式里,除了描述生產者、消費者、面包,還需要描述籃子這個容器。假設使用數組作為容器,生產者每生產一個,生產指針向后移位,消費者每消費一個,消費指針向后移位。

代碼如下:可參考API-->Condition類中給出的示例代碼

import java.util.concurrent.locks.*;class Basket { private Bread[] arr; //the size of basket Basket(int size){  arr = new Bread[size]; } //the pointer of in and out private int in_ptr,out_ptr; //how many breads left in basket private int left; private Lock lock = new ReentrantLock(); private Condition full = lock.newCondition(); private Condition empty = lock.newCondition(); //bread into basket public void in(){  lock.lock();  try{   while(left == arr.length){    try{full.await();} catch (InterruptedException i) {i.printStackTrace();}   }   arr[in_ptr] = new Bread("MianBao",Producer.num++);   System.out.println("Put the bread: "+arr[in_ptr].getName()+"------into basket["+in_ptr+"]");   left++;   if(++in_ptr == arr.length){in_ptr = 0;}   empty.signal();  } finally {   lock.unlock();  } } //bread out from basket public Bread out(){  lock.lock();  try{   while(left == 0){    try{empty.await();} catch (InterruptedException i) {i.printStackTrace();}   }   Bread out_bread = arr[out_ptr];   System.out.println("Get the bread: "+out_bread.getName()+"-----------from basket["+out_ptr+"]");   left--;   if(++out_ptr == arr.length){out_ptr = 0;}   full.signal();   return out_bread;  } finally {   lock.unlock();  } }}class Bread { private String name; Bread(String name,int num){  this.name = name + num; } public String getName(){  return this.name; }}class Producer implements Runnable { private Basket basket; public static int num = 1; //the first number for Bread's name Producer(Basket b){  this.basket = b; } public void run(){  while(true) {   basket.in();   try{Thread.sleep(10);}catch(InterruptedException i){}  } }}class Consumer implements Runnable { private Basket basket; private Bread i_get; Consumer(Basket b){  this.basket = b; } public void run(){  while(true){   i_get = basket.out();   try{Thread.sleep(10);}catch(InterruptedException i){}  } }}public class ProduceConsume_7 { public static void main(String[] args) {  Basket b = new Basket(20); // the basket size = 20  Producer pro = new Producer(b);  Consumer con = new Consumer(b);  Thread pro_t1 = new Thread(pro);  Thread pro_t2 = new Thread(pro);  Thread con_t1 = new Thread(con);  Thread con_t2 = new Thread(con);  Thread con_t3 = new Thread(con);  pro_t1.start();  pro_t2.start();  con_t1.start();  con_t2.start();  con_t3.start(); }}

這里涉及了消費者、生產者、面包和籃子,其中面包和籃子是多線程共同操作的資源,生產者線程生產面包放進籃子,消費者線程從籃子中取出面包。理想的代碼是將生產任務和消費任務都封裝在資源類中,因為面包是籃子容器的元素,所以不適合封裝到面包類中,而且封裝到籃子中,能更方便地操作容器。

注意,一定要將所有涉及資源操作的代碼都放進鎖的內部,否則會產生多線程不同步問題。例如,在Producer類中定義了生產面包的方法produce(),然后將其作為放進籃子的方法basket.in()的參數,即basket.in(producer()),這是錯誤的行為,因為produce()是在鎖的外部執行后才傳遞給in()方法的。

以上這篇基于Java 生產者消費者模式(詳細分析)就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持VeVb武林網。


注:相關教程知識閱讀請移步到JAVA教程頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
18性欧美xxxⅹ性满足| 亚洲福利在线看| 91产国在线观看动作片喷水| 欧美精品一区三区| 国产区精品视频| 欧美激情网站在线观看| 秋霞成人午夜鲁丝一区二区三区| 国产精品亚洲一区二区三区| 久久网福利资源网站| 热门国产精品亚洲第一区在线| 日韩精品一二三四区| 亚洲综合av影视| 国产女人18毛片水18精品| 欧美日韩亚洲激情| 久久久精品久久久| 亚洲伊人一本大道中文字幕| 国产精品免费看久久久香蕉| 久久免费少妇高潮久久精品99| 欧美精品videos另类日本| 精品视频久久久久久| 亚洲天堂av网| 中文字幕国产亚洲2019| 亚洲www在线观看| 欧美国产日韩一区二区三区| 欧美一二三视频| 国产精品视频在线播放| 欧美香蕉大胸在线视频观看| 欧美成人精品在线视频| 国产精品视频在线观看| 精品福利一区二区| 欧美交受高潮1| 亚洲最大福利网| 亚洲黄色av网站| 亚洲有声小说3d| 久久中文字幕一区| 中文字幕最新精品| 欧美成人激情在线| 久久久99免费视频| 国内精品久久久久影院 日本资源| 中文字幕免费精品一区| 日韩欧美黄色动漫| 中文字幕在线观看日韩| 92国产精品视频| 亚洲精品av在线| 国产精品一二三在线| 国产精品夜间视频香蕉| 亚洲网站在线播放| 中文字幕欧美日韩在线| 中文字幕在线亚洲| 91丝袜美腿美女视频网站| 欧美国产乱视频| 91亚洲一区精品| 国产成人久久久精品一区| 国语自产精品视频在免费| 国产成人综合久久| 日本老师69xxx| 欧美电影在线观看| 亚洲国产97在线精品一区| 久久激情五月丁香伊人| 久久中文字幕在线| 亚洲一区二区三区sesese| 2020久久国产精品| 精品中文字幕在线2019| 亚洲网站在线播放| 影音先锋欧美在线资源| 亚洲欧美国产精品va在线观看| 欧美超级乱淫片喷水| 理论片在线不卡免费观看| 亚洲精品视频网上网址在线观看| 最近2019中文字幕mv免费看| 97精品视频在线| 5278欧美一区二区三区| 国产精品麻豆va在线播放| 欧美视频中文字幕在线| 亚洲一区二区精品| 欧美大片在线看免费观看| 国产精品www色诱视频| 亚洲欧美国产精品va在线观看| 国产噜噜噜噜噜久久久久久久久| 久久久久国产一区二区三区| 欧美日韩亚洲视频一区| 欧美视频精品一区| 久久精品国亚洲| 亚洲美女福利视频网站| 欧美怡红院视频一区二区三区| 91在线免费观看网站| 国产精品久久久久久久av大片| 久久久av免费| 日韩一区二区三区在线播放| 欧美肥老太性生活视频| 亚洲视频axxx| 91精品国产91久久久久| 夜夜嗨av色综合久久久综合网| 欧美激情在线有限公司| 亚洲91精品在线观看| 亚洲国产天堂久久综合| 国产精品极品美女在线观看免费| 国产欧美精品日韩精品| 亚洲成人网在线观看| 日韩av第一页| 国产精品啪视频| 国产精品白嫩美女在线观看| 久久久久久一区二区三区| 亚洲深夜福利在线| 欧美午夜视频在线观看| 97热精品视频官网| 久久精品91久久久久久再现| 欧美xxxx18国产| 久久精品视频中文字幕| 欧美性猛交xxxx黑人猛交| 欧美精品免费在线观看| 国色天香2019中文字幕在线观看| 欧美精品久久久久久久免费观看| 亚洲精品一区中文字幕乱码| 亚洲少妇中文在线| 国产在线一区二区三区| 中文字幕在线看视频国产欧美| 国产精品入口尤物| 91大神福利视频在线| 欧美日本高清一区| 午夜精品久久久久久久久久久久| 日韩欧美一区二区三区| 色中色综合影院手机版在线观看| 黑人极品videos精品欧美裸| 欧美精品激情视频| 欧美在线影院在线视频| 91地址最新发布| 国产+成+人+亚洲欧洲| 日韩电影中文字幕在线观看| 中文字幕欧美精品在线| 国外色69视频在线观看| 亚洲白拍色综合图区| 欧美野外wwwxxx| 亚洲精品自拍偷拍| 色综合久久88色综合天天看泰| 国产精品pans私拍| 欧美午夜久久久| 中文字幕久久久av一区| 日本中文字幕成人| 午夜欧美大片免费观看| 777精品视频| 在线精品高清中文字幕| 亚洲欧美中文日韩v在线观看| 91av在线播放| 国产狼人综合免费视频| 亚洲第一av网| 日韩中文视频免费在线观看| 亚洲自拍偷拍色片视频| 这里只有精品视频在线| 欧美激情欧美激情| 久久久久久久久电影| 欧美综合在线观看| 成人激情视频小说免费下载| 国产精品羞羞答答| 欧美日本高清一区| 久久久久久成人| 性夜试看影院91社区| 不卡在线观看电视剧完整版| 国自产精品手机在线观看视频| 91九色在线视频| 国产精品视频1区| 久久亚洲综合国产精品99麻豆精品福利| 成人精品在线观看| 亚洲国产日韩精品在线|