<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                # Queue接口 Queue 隊列,也是Collection的一個重要分支,是一個先進先出的結構,其一個重要的子接口為BlockingQueue(阻塞隊列),阻塞隊列在多線程的場景中有著廣泛的應用,在這里主要學習BlockingQueue的實現類。 阻塞隊列中的阻塞意為等待,與非阻塞隊列相比的話有如下的不同點: 1. 出隊: * 非阻塞隊列:當隊列滿的時候,放入新的元素時,數據丟失。 * 阻塞隊列:當隊列滿的時候,放入新的元素時,線程阻塞等待,等待隊列中有出隊的元素,再繼續運行放進去。 2. 出隊: * 非阻塞隊列:當隊列沒有元素的時候,取數據時得到的是null。 * 阻塞隊列:當隊列沒有元素的時候,取數據時,線程阻塞等待,什么時候有元素入隊了,才可以繼續運行取出元素。 常用API: 1. 添加元素 ~~~ ?public boolean add(E, e); // 不能添加null,成功時返回true,不成功時拋出Queue Full異常 ?public boolean offer(E, e); // 不能添加null,成功時返回true,不成功時返回false ?public void put(E e); // 阻塞操作 ~~~ 2. 查詢 ~~~ ?public E take(); // 獲取并移除此隊列的頭部,在元素變得可用之前一直等待 ?public E poll(long time, TimeUnit unit); // 獲取并移除此隊列的頭部,在指定時間內等待可用的元素 ?public E peek(); // 獲取隊列的頭部,不移除 ? ~~~ &nbsp; ***** BlockingQueue的常見實現類: ## ArrayBlockingQueue ArrayBlockingQueue底層是一個基于數組的有邊界的阻塞隊列,其只用了一把鎖來同時阻塞讀寫操作,讀寫不分離。 源碼分析: ~~~ ?public class ArrayBlockingQueue<E> extends AbstractQueue<E> ? ? ? ? ?implements BlockingQueue<E>, java.io.Serializable { ? ? ?// 底層使用數組保存數據 ? ? ?final Object[] items; ? ? ?// 取元素時用到的索引,初始為0 ? ? ?int takeIndex; ? ? ?// 放元素時用到的索引,初始為0 ? ? ?int putIndex; ? ? ?// 數組中元素的個數 ? ? ?int count; ? ? ?// 可重入鎖,用于出隊和入隊等操作中 ? ? ?final ReentrantLock lock; ? ? ?// lock伴隨的一個不為空的等待池,當隊列有數據時會喚醒在該等待隊列中的線程獲取數據 ? ? ?private final Condition notEmpty; ? ? ?// lock伴隨的一個隊列不滿的等待池,當隊列中還沒有滿時會喚醒在該等待隊列中的線程往隊列添加數據 ? ? ?private final Condition notFull; ? ? ? ? ? ? ? ? ?// -----------------------構造方法------------------------- ? ? ?public ArrayBlockingQueue(int capacity) { // 必須指定初始化容量 ? ? ? ? ?this(capacity, false); // 默認為非公平鎖 ? ? } ? ? ?public ArrayBlockingQueue(int capacity, boolean fair) { ? ? ? ? ?if (capacity <= 0) ? ? ? ? ? ? ?throw new IllegalArgumentException(); ? ? ? ? ?// 底層沒有擴容邏輯 ? ? ? ? ?this.items = new Object[capacity]; ? ? ? ? ?lock = new ReentrantLock(fair); ? ? ? ? ?// 使用同一個鎖的等待池,因此讀寫操作是不分離的,會被同把鎖所阻塞 ? ? ? ? ?notEmpty = lock.newCondition(); ? ? ? ? ?notFull = ?lock.newCondition(); ? ? } ? ? ? ? ? ?//------------------兩個關鍵的操作------------------------- ? ? ?// 入隊 ? ? ?private void enqueue(E x) { ? ? ? ? ?final Object[] items = this.items; ? ? ? ? ?// 將x放入putIndex索引指向的位置 ? ? ? ? ?items[putIndex] = x; ? ? ? ? ?if (++putIndex == items.length) ? ? ? ? ? ? ?// 循環數組的作用,可以不斷的利用底層數組存放數據 ? ? ? ? ? ? ?putIndex = 0; ? ? ? ? ?// 元素個數+1 ? ? ? ? ?count++; ? ? ? ? ?// 添加一個元素則證明底層數組有數據了,喚醒在空隊列的等待池中阻塞的線程取出數據,即喚醒take方法的阻塞 ? ? ? ? ?notEmpty.signal(); ? ? } ? ? ?// 入隊 ? ? ?private E dequeue() { ? ? ? ? ?final Object[] items = this.items; ? ? ? ? ?@SuppressWarnings("unchecked") ? ? ? ? ?E x = (E) items[takeIndex]; ? ? ? ? ?items[takeIndex] = null; ? ? ? ? ?if (++takeIndex == items.length) ? ? ? ? ? ? ?// 循環數組 ? ? ? ? ? ? ?takeIndex = 0; ? ? ? ? ?count--; // 元素個數-1 ? ? ? ? ?if (itrs != null) ? ? ? ? ? ? ?itrs.elementDequeued(); ? ? ? ? ?// 取出一個元素則證明隊列中又有空間可以存放數據了,喚醒在滿隊列的等待池中阻塞的線程,可以往隊列中繼續添加數據,即喚醒put方法的阻塞 ? ? ? ? ?notFull.signal(); ? ? ? ? ?return x; ? ? } ? ? ? ? ? ?// -------------------阻塞操作------------------------- ? ? ?// 添加元素 ? ? ?public void put(E e) throws InterruptedException { ? ? ? ? ?checkNotNull(e); // 檢查是否為空 ? ? ? ? ?// 線程安全的添加元素 ? ? ? ? ?final ReentrantLock lock = this.lock; ? ? ? ? ?lock.lockInterruptibly(); ? ? ? ? ?try { ? ? ? ? ? ? ?while (count == items.length) ? ? ? ? ? ? ? ? ?// 元素滿了的話將當前線程放入已滿等待池中阻塞 ? ? ? ? ? ? ? ? ?notFull.await(); ? ? ? ? ? ? ?// 入隊操作-可能會喚醒在非空等待池中阻塞的隊列 ? ? ? ? ? ? ?enqueue(e); ? ? ? ? } finally { ? ? ? ? ? ? ?lock.unlock(); ? ? ? ? } ? ? } ? ? ? ? ? ?// 取出元素 ? ? ?public E take() throws InterruptedException { ? ? ? ? ?final ReentrantLock lock = this.lock; ? ? ? ? ?lock.lockInterruptibly(); ? ? ? ? ?try { ? ? ? ? ? ? ?while (count == 0) ? ? ? ? ? ? ? ? ?// 如果沒有元素的話,則會將當前線程放入非空等待池中阻塞 ? ? ? ? ? ? ? ? ?notEmpty.await(); ? ? ? ? ? ? ?return dequeue(); ? ? ? ? } finally { ? ? ? ? ? ? ?lock.unlock(); ? ? ? ? } ? ? } ? ? ? ?} ~~~ 【更正:Condition中等待池這個詞匯換成等待隊列可能會更合適一點。】 總結: 1. 兩個Conditional變量的作用,notEmpty用來阻塞獲取元素操作,當隊列非空時(enqueue中喚醒)會喚醒該等待池中的線程;notFull用來阻塞添加元素操作,當隊列未滿時(dequeue中喚醒)會喚醒該等待池中的線程。 2. 讀寫操作只用到一把鎖,讀寫不分離。 3. 必須指定初始化容量。對于put操作當隊列已滿的時候會阻塞線程,對于take操作當隊列為空時也會阻塞線程。 > 面試:能否將while(count == items.length)和while(count == 0)中的while換成if? 不能,因為notFull中的線程被喚醒的瞬間,有其他線程放入元素,此時隊列又滿了,如果采用if的話只會判斷一次,線程被喚醒之后就會繼續執行enqueue操作,但是此時隊列是滿的,執行enqueue就會造成數據的丟失了。因此需要不斷的判斷,直到喚醒的線程判斷出當前隊列真的有位置了才能繼續運行。 &nbsp; ## LinkedBlockingQueue 底層基于鏈表結構,支持讀寫同時操作,并發情況下,效率比ArrayBlockingQueue高。是一個`可選擇`的有界隊列,可以指定鏈表的容量,當不指定時默認為整型的最大值。 其API的使用與ArrayBlockingQueue一樣。 源碼分析: ~~~ ?public class LinkedBlockingQueue<E> extends AbstractQueue<E> ? ? ? ? ?implements BlockingQueue<E>, java.io.Serializable { ? ? ? ? ? ?// 鏈表的節點結構 ? ? ?static class Node<E> { ? ? ? ? ?E item; // 數據域 ? ? ? ? ?Node<E> next; // 下個節點的指針域 ? ? ? ? ?Node(E x) { item = x; } ? ? } ? ? ?// 鏈表的最大容量,不指定時默認為Integer.MAX_VALUE ? ? ?private final int capacity; ? ? ?// 鏈表中元素的個數,因為讀寫操作是分離的,所以要使用原子操作來改變元素的個數 ? ? ?private final AtomicInteger count = new AtomicInteger(); ? ? ?// 頭結點 ? ? ?transient Node<E> head; ? ? ?// 尾節點 ? ? ?private transient Node<E> last; ? ? ?// 獲取數據的鎖 ? ? ?private final ReentrantLock takeLock = new ReentrantLock(); ? ? ?// 隊列非空等待池 ? ? ?private final Condition notEmpty = takeLock.newCondition(); ? ? ?// 添加數組的鎖 ? ? ?private final ReentrantLock putLock = new ReentrantLock(); ? ? ?// 隊列不滿時的等待池 ? ? ?private final Condition notFull = putLock.newCondition(); ? ? ? ? ? ?//---------------------構造方法---------------------- ? ? ?public LinkedBlockingQueue() { ? ? ? ? ?// 默認容量為整型最大值 ? ? ? ? ?this(Integer.MAX_VALUE); ? ? } ? ? ?public LinkedBlockingQueue(int capacity) { ? ? ? ? ?if (capacity <= 0) throw new IllegalArgumentException(); ? ? ? ? ?this.capacity = capacity; ? ? ? ? ?last = head = new Node<E>(null); ? ? } ? ? ? ? ? ? ? ? ?//--------------------關鍵操作---------------------------- ? ? ?// 入隊, ? ? ?private void enqueue(Node<E> node) { ? ? ? ? ?// 尾插法,直接添加到最后一個元素,并改變last的指向 ? ? ? ? ?last = last.next = node; ? ? } ? ? ?// 出隊 ? ? ?private E dequeue() { ? ? ? ? ?Node<E> h = head; ? ? ? ? ?// 第一個節點,會成為新的頭節點, ? ? ? ? ?Node<E> first = h.next; ? ? ? ? ?h.next = h; // help GC,這樣操作沒有別的引用h這個節點,垃圾回收會進行回收 ? ? ? ? ?head = first; ? ? ? ? ?// 返回第一個節點的數據 ? ? ? ? ?E x = first.item; ? ? ? ? ?// head的數據域為空 ? ? ? ? ?first.item = null; ? ? ? ? ?return x; ? ? } ? ? ? ? ? ?// ----------------put和take操作------------------------- ? ? ?// 添加元素 ? ? ?public void put(E e) throws InterruptedException { ? ? ? ? ?if (e == null) throw new NullPointerException(); ? ? ? ? ?int c = -1; ? ? ? ? ?// 封裝成新的節點結構 ? ? ? ? ?Node<E> node = new Node<E>(e); ? ? ? ? ?final ReentrantLock putLock = this.putLock; ? ? ? ? ?final AtomicInteger count = this.count; ? ? ? ? ?putLock.lockInterruptibly(); ? ? ? ? ?try { ? ? ? ? ? ? ?while (count.get() == capacity) { ? ? ? ? ? ? ? ? ?// 隊列已滿,則阻塞當期線程,采用while的原因與ArrayBlockingQueue相同 ? ? ? ? ? ? ? ? ?notFull.await(); ? ? ? ? ? ? } ? ? ? ? ? ? ?enqueue(node); ? ? ? ? ? ? ?// 遞增元素個數 ? ? ? ? ? ? ?c = count.getAndIncrement(); ? ? ? ? ? ? ?if (c + 1 < capacity) ? ? ? ? ? ? ? ? ?// 再次判斷隊列是否滿的,因為count可能同時被讀線程操作 ? ? ? ? ? ? ? ? ?notFull.signal(); ? ? ? ? } finally { ? ? ? ? ? ? ?putLock.unlock(); ? ? ? ? } ? ? ? ? ?if (c == 0) ? ? ? ? ? ? ?// 隊列中有元素了,喚醒take阻塞的線程,這里的c是舊值,count=1 ? ? ? ? ? ? ?signalNotEmpty(); ? ? } ? ? ?// 取出元素 ? ? ?public E take() throws InterruptedException { ? ? ? ? ?E x; ? ? ? ? ?int c = -1; ? ? ? ? ?final AtomicInteger count = this.count; ? ? ? ? ?final ReentrantLock takeLock = this.takeLock; ? ? ? ? ?takeLock.lockInterruptibly(); ? ? ? ? ?try { ? ? ? ? ? ? ?while (count.get() == 0) { ? ? ? ? ? ? ? ? ?notEmpty.await(); ? ? ? ? ? ? } ? ? ? ? ? ? ?x = dequeue(); ? ? ? ? ? ? ?// 原子的遞減元素個數 ? ? ? ? ? ? ?c = count.getAndDecrement(); ? ? ? ? ? ? ?if (c > 1) ? ? ? ? ? ? ? ? ?// 再次判斷是否有元素 ? ? ? ? ? ? ? ? ?notEmpty.signal(); ? ? ? ? } finally { ? ? ? ? ? ? ?takeLock.unlock(); ? ? ? ? } ? ? ? ? ?if (c == capacity) ? ? ? ? ? ? ?// c是count的舊值,c==capacity就證明隊列有空余出來的位置了,喚醒put阻塞的線程 ? ? ? ? ? ? ?signalNotFull(); ? ? ? ? ?return x; ? ? } ?} ~~~ > 在FixedThreadPool和SingleThreadExecutor中被使用。 > 疑問,為什么使用了鎖還要使用while進行排隊隊列是否為空或者隊列是否滿了? > 這是因為底層使用的是ReetrantLock,支持可重復入的。 &nbsp; **ArrayBlockingQueue和LinkedBlockingQueue的對比** 假如有10000個線程,分別有5000個線程進行讀操作,有5000個線程進行寫操作,其性能對比如下: ~~~ ?public static void main(String[] args) throws InterruptedException { ? ? ?ArrayBlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(10000); ? ? ?// LinkedBlockingQueue<String> linkedBlockingQueue = new LinkedBlockingQueue<>(10000); ? ? ?List<String> dataList = new Vector<>(); ? ? ?// 創建10000個線程,分別進行500次的讀寫操作 ? ? ?long start = System.currentTimeMillis(); ? ? ?for (int i = 0; i < 10000; i++) { ? ? ? ? ?if (i % 2 == 0) { ? ? ? ? ? ? ?new Thread(() -> { ? ? ? ? ? ? ? ? ?for (int j = 0; j < 500; j++) { ? ? ? ? ? ? ? ? ? ? ?try { ? ? ? ? ? ? ? ? ? ? ? ? ?blockingQueue.put(Thread.currentThread().getName() + "j"); ? ? ? ? ? ? ? ? ? ? } catch (InterruptedException e) { ? ? ? ? ? ? ? ? ? ? ? ? ?e.printStackTrace(); ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } ? ? ? ? ? ? }, "Thread-" + i).start(); ? ? ? ? } else { ? ? ? ? ? ? ?new Thread(() -> { ? ? ? ? ? ? ? ? ?for (int j = 0; j < 500; j++) { ? ? ? ? ? ? ? ? ? ? ?try { ? ? ? ? ? ? ? ? ? ? ? ? ?dataList.add(blockingQueue.take()); ? ? ? ? ? ? ? ? ? ? } catch (InterruptedException e) { ? ? ? ? ? ? ? ? ? ? ? ? ?e.printStackTrace(); ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? } ? ? ? ? ? ? }, "Thread-" + i).start(); ? ? ? ? } ?? ? ? } ? ? ?long end = System.currentTimeMillis(); ? ? ?System.out.println("用時:" + (end - start) + "ms"); ? ? ?System.out.println(dataList.size()); ?} ~~~ 結果: ~~~ ?ArrayBlockingQueue用時:2331ms ?LinkedBlockingQueue用時:2319ms ~~~ 兩者性能差不多,有時LinkedBlockingQueue的性能還比不上ArrayBlockingQueue。 &nbsp; ## SynchronousQueue 使用這個隊列必須先從隊列中取出元素(即先調用take方法),才可以向隊列中加入元素(再調用put方法),SynchronousQueue隊列中沒有任何容量,甚至一個容量都沒有。可以理解一個標記,當一個線程從隊列獲取數據的時候就會打上一個標記,之后如果有另外一個線程放入數據,數據就會直接傳送給獲取數據的線程。 如果先向隊列中添加元素則會拋出異常`Queue Full`,因為隊列是沒有容量的。使用put方法的話阻塞,因為一開始隊列就是滿的。注意取出元素的方法不能用peek,因為peek不會將元素從隊列中拿走,只是查看的作用。 優點:方便高效的進行線程間的數據傳送,效率高,不會產生隊列中數據爭搶問題。 ~~~ ?public static void main(String[] args) throws InterruptedException { ? ? ? ? ?SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>(); ? ? ? ? ?new Thread(() -> { ? ? ? ? ? ? ?try { ? ? ? ? ? ? ? ? ?System.out.println(Thread.currentThread().getName() + ":" + synchronousQueue.take()); ? ? ? ? ? ? } catch (InterruptedException e) { ? ? ? ? ? ? ? ? ?e.printStackTrace(); ? ? ? ? ? ? } ? ? ? ? }, "Thread-take").start(); ? ? ? ? ?new Thread(() -> { ? ? ? ? ? ? ?try { ? ? ? ? ? ? ? ? ?synchronousQueue.put("abc"); ? ? ? ? ? ? } catch (InterruptedException e) { ? ? ? ? ? ? ? ? ?e.printStackTrace(); ? ? ? ? ? ? } ? ? ? ? }, "Thread-put").start(); ?? ? ? } ~~~ 要多個線程配合使用才行。 > 在CachedThreadPool中被使用 &nbsp; ## PriorityBlockingQueue 帶有優先級的阻塞隊列,隊列中的元素有不同的優先級;沒有界限限制(整型最大值),但是可以指定初始化的長度,不指定時默認為11,底層會進行擴容操作。放入的元素必須實現內部比較器,或者在創建PriorityBlockingQueue時設置外部比較器,元素的優先級根據比較器來決定。 ~~~ ?public static void main(String[] args) throws InterruptedException { ? ? ?PriorityBlockingQueue<String> blockingQueue = new PriorityBlockingQueue<>(11, new Comparator<String>() { ? ? ? ? ?@Override ? ? ? ? ?public int compare(String o1, String o2) { ? ? ? ? ? ? ?return o1.compareTo(o2); ? ? ? ? } ? ? }); ?? ? ? ?blockingQueue.put("bbb"); ? ? ?blockingQueue.put("aaa"); ? ? ?blockingQueue.put("ccc"); ? ? ?System.out.println(blockingQueue); ? ? ?System.out.println("------------------------------------------------------"); ? ? ?System.out.println(blockingQueue.take()); ? ? ?System.out.println(blockingQueue.take()); ? ? ?System.out.println(blockingQueue.take()); ?? ?} ~~~ &nbsp; ## DelayQueue DelayQueue是一個無界的BlockingQueue,用于放置實現了Delayed接口的對象,存放在DelayQueue隊列中的對象只能在其到期時才能從隊列中取走。 當生產者線程調用put方法添加元素時,會觸發Delayed接口中的compareTo方法進行排序,也就是隊列中的元素的順序是按到期時間排序的,而非進入隊列的順序。排在隊列頭部的元素最早到期,越往后到期時間越晚。 消費者線程查看隊列頭部的元素,并不是取出操作。然后調用元素的getDelay方法,如果此方法的返回值≤0,則消費者線程會從隊列中取出此元素處理。如果getDealy方法返回值大于0,則消費者線程wait返回的時間值后,再次從隊列頭部取出元素,此時元素到期可以取出。 注意:不能將null元素放置到這種隊列中。 使用場景: 1. 淘寶訂單業務:下單之后如果30分鐘之內沒有付款就自動取消訂單。 2. 餓了么訂餐通知:下單成功后60S之內給用戶發送短信通知。 3. 關閉空閑連接:服務器中客戶端的連接空閑一段時間后會自動關閉。 4. 緩存:緩存中的對象超過空閑時間限制,需要從緩存中移出。 5. 任務超時處理:在tcp協議的滑動窗口中,用來處理超時未響應的請求。 ~~~ ?public class TestDelayQueue { ? ? ?public static void main(String[] args) throws InterruptedException { ? ? ? ? ?DelayQueue<Student> delayQueue = new DelayQueue<>(); ? ? ? ? ?delayQueue.put(new Student("zhangsan", 20, System.currentTimeMillis() + 5000)); ? ? ? ? ?delayQueue.put(new Student("lisi", 19, System.currentTimeMillis() + 2000)); ? ? ? ? ?delayQueue.put(new Student("wangwu", 18, System.currentTimeMillis() + 1000)); ? ? ? ? ?System.out.println(delayQueue); ? ? ? ? ?System.out.println(delayQueue.take()); ? ? ? ? ?System.out.println(delayQueue.take()); ? ? ? ? ?System.out.println(delayQueue.take()); ? ? } ?} ?? ?// Student.java ?public class Student implements Delayed { ?? ? ? ?private String name; ? ? ?private int age; ? ? ?private long endTime; ?? ? ? ?public Student() {} ?? ? ? ?public Student(String name, int age, long endTime) { ? ? ? ? ?this.name = name; ? ? ? ? ?this.age = age; ? ? ? ? ?this.endTime = endTime; ? ? } ?? ? ? ?@Override ? ? ?public long getDelay(TimeUnit unit) { ? ? ? ? ?return endTime - System.currentTimeMillis(); ? ? } ?? ? ? ?@Override ? ? ?public int compareTo(Delayed o) { ? ? ? ? ?Student other = (Student) o; ? ? ? ? ?return age - other.getAge(); ? ? } ?? ? ? ?public String getName() { ? ? ? ? ?return name; ? ? } ?? ? ? ?public void setName(String name) { ? ? ? ? ?this.name = name; ? ? } ?? ? ? ?public int getAge() { ? ? ? ? ?return age; ? ? } ?? ? ? ?public void setAge(int age) { ? ? ? ? ?this.age = age; ? ? } ?? ? ? ?@Override ? ? ?public String toString() { ? ? ? ? ?return "Student{" + ? ? ? ? ? ? ? ? ?"name='" + name + '\'' + ? ? ? ? ? ? ? ? ?", age=" + age + ? ? ? ? ? ? ? ? ?", endTime=" + endTime + ? ? ? ? ? ? ? ? ?'}'; ? ? } ?} ~~~ 實現Delayed接口的實現類需要重寫getDelay和compareTo方法。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看