信号量与 PV 操作的 Java 讲解

程序人生 · yasic · Created at · Last by yt5206978 Replied at · 2635 hits
213 1485143427

前言

原文地址:信号量与 PV 操作的 Java 讲解

信号量(Semaphore)是由 Edsger Dijkstra 在 设计 THE Multiprogramming System 时提出的一种概念,用以解决进程通信或线程通信时的同步与互斥问题,主要包含两种操作, P 操作和 V 操作。

提到信号量就必须提到并发编程,并发是一种在时间上将多个逻辑控制流进行重叠的机制,也是现代计算机系统最显著和重要的特性。

程序计数器中指令的地址的过渡称为控制转移,控制转移的序列称为处理器的控制流。操作系统利用进程为每一个应用程序提供了一种独占处理器的假象,表现在时间上就是多个逻辑控制流的重叠,也即并发。更准确的定义是,逻辑控制流 X 和流 Y 互相并发,当且仅当 X 在 Y 开始之后和 Y 结束之前开始,或 Y 在 X 开始之后和 X 结束之前开始。

使用应用级并发的应用程序称为并发程序,现代操作系统提供了三种基本的构造并发程序的方法:

  • 进程 由内核调度的逻辑控制流,进程间利用 IPC 进程间通信机制进行通信,共享数据很困难
  • I/O 多路复用 应用创建自己的逻辑流,共享同一个进程的虚拟地址空间,并利用 I/O 多路复用来显式调用流,因此编码比较复杂,而且不能充分利用多核处理器
  • 线程 运行在进程上下文中的控制流,由内核自动调度,共享所属进程的整个虚拟地址空间,结合了上面两种方法的特性

多线程与共享变量

在基于多线程的并发编程中绕不开对共享变量的使用。一个变量是共享的,当且仅当这个变量的实例被一个以上的线程引用。

一组并发线程运行在一个进程的上下文中,每一个线程都有自己的独立线程上下文,包括线程 ID、栈、栈指针、程序计数器 PC以及通用目的寄存器。每一个线程和其他线程一起共享进程上下文的剩余部分,包括用户虚拟地址空间和进程打开的文件集合。寄存器从不共享,但虚拟存储器总是共享。

共享变量对于多线程协作是非常方便的,但也因此带来了一些棘手的问题,主要有两方面:

  • 互斥问题
  • 同步问题

互斥问题

先从一个简单的例子出发。假设我们有一个银行 Bank,银行中预存了 10000 元钱,有两个人分别需要向银行存 10000 元钱,钱比较多,因此不能立刻存进去,我们可以用两个线程完成这个操作,我们的预期目标应该是最终银行里会有 30000 元钱。

首先我们定义一个银行 Bank。

public class Bank {
    private int money = 0;

    public Bank(int money){
        this.money = money;
    }

    public void addMoney(){
        this.money++;
    }

    public int readMoney(){
        return this.money;
    }
}

Bank 提供两个方法,addMoney 将银行的金额自增 1,而 readMoney 读出银行现在的总金额。

接下来,实现一个线程用来存钱。Java中实现线程的方法有三种:

  • 继承 Thread 类
  • 实现 Runnable 接口
  • 实现 Callable 接口

一般来说实现接口比继承类的拓展性更强,这里我们采取第二种方式实现一个 AddMoneyThread 类。

public class AddMoneyThread implements Runnable {
    private Bank bank;
    private int addMoneyCount;
    public int id;

    public AddMoneyThread(Bank bank, int addMoneyCount, int id) {
        this.bank = bank;
        this.addMoneyCount = addMoneyCount;
        this.id = id;
    }

    @Override
    public void run() {
        if (bank != null) {
            int index = 1;
            while (index <= this.addMoneyCount) {
                bank.addMoney();
                index++;
            }
        }
    }
}

可以看到,在构造方法里我们传入了一个 Bank 实例,以及需要存入的金额,在 run 方法里,我们循环调用 Bank 实例的 addMoney 方法增加 Bank 的金额。

接下来是主类,主要是创建并执行线程。

public class MutualExclusion {

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            Bank bank = new Bank(10000);
            new Thread(new AddMoneyThread(bank, 10000, 1)).start();
            new Thread(new AddMoneyThread(bank, 10000, 2)).start();
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("第" + (i + 1) + "次" + bank.readMoney());
            }
        }
    }
}

在这里可以看到我们初始化 Bank 的金额为 10000,而我们创建并执行了两个 AddMoneyThread 线程,分别向 Bank 里存入 10000 元钱。为了对比效果,我们将这个过程执行了 5 次。

最终运行的结果如下:

第1次:20949
第2次:22487
第3次:22481
第4次:22209
第5次:22425

可以看到,并不是我们期望的 30000 元,而且每一次执行的结果还不一样。

那么其中到底哪里出了问题呢?

并发编程最大的缺点就是很难复现每一次执行过程,对于多个线程,我们无法预测下一次的执行顺序,也不能依据某种执行顺序的假设来设计我们的代码。仔细分析线程部分代码,我们不加保护地进行了如下操作

bank.addMoney();

 Bank 类中
public void addMoney(){
    this.money++;
}

其实这里对于 money 变量自增一的操作就带来了线程安全问题。

根据《Java Concurrency in Practice》的定义,一个线程安全的 class 应当满足以下三个条件:
• 多个线程同时访问时,其表现出正确的行为。
• 无论操作系统如何调度这些线程, 无论这些线程的执行顺序如何交织(interleaving)。
• 调用端代码无须额外的同步或其他协调动作。

而这里对 money 自增一的操作并不是原子操作,也就是说不是 不可分割的操作 ,之所以这样说是因为在 Java 中变量自增一其实经历了 "读取-修改-写入" 三个步骤

  • 读取:读取 money 变量的值
  • 修改:修改 值为原值加一
  • 写入:将计算结果写回到 money 变量中

那么这里带来的线程安全问题就是,假如有两个线程 A 和 B 同时调用 addMoney 函数时,A完成了读取和修改步骤时,假设 money 等于 10,被 A 修改后为 11 了,然后 B 完成了读取步骤,因为 A 尚未写回新的值,B 读到 money 依然为 10,于是 B 将值增一,而后 A 和 B 分别写回 money ,结果 money 值变为了 11,凭空丢失掉了一次自增一操作。

具体可以看下图

|序列|线程|步骤|money|
|--|--|--|--|
|1|A|读取|10|
|2|A|修改|10|
|3|B|读取|10|
|4|B|修改|11|
|5|A|写入|11|
|6|B|写入|11|

这样的步骤是完全无法预测的,因而结果也就不尽相同了。究其原因,还是因为 Bank 实例只有一个,money 也只有一个,两个线程同时访问并修改共享变量时互相之间的行为是互斥的,它们互相竞争对资源的读写访问控制,这一类问题被称为并发编程的 互斥问题

同步问题

再说一个不一样的例子。现在我们有一个仓库 Repository,Repository 的大小为 10, 有 A 和 B 两个人,A 负责向 Repsitory 里存入 10000 份货物 Goods,B 负责从 Repository 里拿出所有的 Goods。我们可以用两个线程分别完成 A 和 B 的工作,我们的预期目标是最终 B 应该可以拿出来 10000 份货物。

当然很明显,这里仓库的大小相对于 A 要存入的货物量是非常小的,所以 A 有可能遇到仓库放满了货物而不能存入的情景,而 B 则可能遇到 仓库里没有货物可以取出的情景,让我们看看会出现什么问题吧。

首先让我们定义一个仓库 Repository

public class Repository {
    private Goods[] goodsArray;
    private int usedSize = 0;

    public Repository(int goodSize){
        goodsArray = new Goods[goodSize];
    }

    public void saveGood(int goodId) {
        if (usedSize < goodsArray.length) {
            goodsArray[usedSize] = new Goods(goodId);
            usedSize++;
        }
    }

    public Goods takeGood(){
        if (usedSize == 0){
            return null;
        }
        return goodsArray[--usedSize];
    }
}

Repository 提供了两个方法,saveGoood 用来向仓库存放一份货物,而 takeGood 用来拿出一份 Reposotory 存放的货物。这里我们使用了一个数组存放货物,数组大小在构建时确定其大小,同时我们需要一个哨兵变量 usedSize 来维护数组中真正存放了货物的大小。

接下来是存放货物的线程

public class SaveGoodThread implements Runnable{
    private Repository repository;
    private int saveNumber = 0;

    public SaveGoodThread(Repository repository, int saveNumber){
        this.repository = repository;
        this.saveNumber = saveNumber;
    }

    @Override
    public void run(){
        if (repository != null){
            int i = 0;
            while (i < this.saveNumber){
                repository.saveGood(i + 1);
                i++;
            }
        }
    }
}

然后是拿取货物的线程

public class TakeGoodThread implements Runnable {
    private int number = 0;
    private List<Goods> goodsList = null;
    private Repository repository;

    public TakeGoodThread(Repository repository, List<Goods> goodsList) {
        this.repository = repository;
        this.goodsList = goodsList;
    }

    @Override
    public void run() {
        while (true) {
            Goods temp = repository.takeGood();
            number++;
            if (temp != null){
                goodsList.add(temp);
            }
        }
    }
}

在这里要注意我们取出的是有效货物,因此当仓库返回的货物是 null 的时候我们就跳过。我们最终会把货物转存进一个 List 中,这个 goodsList 在构造函数中设置,是由主函数调用并传入的。

下面就是我们的主函数部分。

public class Synchronization {
    public static void main(String[] args) {
        for (int i = 0; i < 5; i++){
            Repository repository = new Repository(10);
            List<Goods> goodsList = new ArrayList<>();
            Thread saveGood = new Thread(new SaveGoodThread(repository, 10000));
            Thread takeGood = new Thread(new TakeGoodThread(repository, goodsList));
            saveGood.start();
            takeGood.start();

            try {
                Thread.sleep(2000);
                saveGood.suspend();
                takeGood.suspend();
                System.out.println("第" + (i + 1) + "次" + goodsList.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

同样的,我们运行了 5 次,来看看最终的结果吧。

第1次1796
第2次3526
第3次3731
第4次10
第5次2657

可以看出,最终拿出来的有效货物总数小于我们存入的 10000 份货物,并且每一次拿出的货物总数也不尽相等,问题出现在哪里了呢?

事实上,正如前面所说的,我们有 A 和 B 两个线程分别完成 save 和 take 两个动作,但是其实这两个动作之间又互相制约,当 Repository 中没有足够空间存放 Goods 时,A 的 save 动作就可能失败,而当 Repository 中没有存放任何 Goods 时,B 的 take 动作也会因此失败。所以其实 save 与 take 动作之间是有暗含的先后关系的,我们称为 同步关系 ,由于没有正确处理同步关系而导致的上述问题我们称为 同步问题

信号量与 PV 操作

为了解决多线程间的互斥问题和同步问题,聪明的 Dijkstra 发明了经典的信号量 Semaphore,Semaphore 只能由两种特殊的操作来处理,即 P 操作和 V 操作。这里我们用 S 代替信号量 Semaphore。

P 操作

  • 如果 S 非零,将 S 减 1
  • 如果 S 小于等于 零,挂起当前线程

V 操作

  • 将 S 加 1
  • 如果 S 小于等于 0,随机重启一个被 P 操作挂起的线程。

当然也有其他版本的 PV 操作可能对于 S 的操作略有不同,但其基本思想都是一致的:利用 PV 操作,将具体的资源数目抽象为信号量。P 操作通过减少信号量来占用多余的资源,当资源资源不足时,则会阻止线程进一步执行从而引发错误。V 操作增加信号量释放已经使用完的资源,并恢复由于资源不足而阻塞等待的线程。如果我们将信号量看作为资源设置的 资源锁,那么 P 操作相当于 加锁操作,而 V 操作相当于 解锁操作 ,由此可以推出,PV 操作都是成对出现的,先通过 P 操作对某资源进行加锁,操作完目标资源后,再通过 V 操作解锁释放资源,具体如下

P(S) //资源加锁

---资源操作---

V(S) //资源解锁

那么接下来就来看看信号量和 PV 操作是如何解决互斥问题和同步问题的。

原子操作

同样的,PV 操作有一个很苛刻的条件就是原子性,即 PV 操作是不可以被中断的,一般在操作系统中会通过 关中断 来实现原子性,关中断是指禁止处理机响应中断源的中断请求,可以通过硬件或执行一条“关中断”指令来实现。

在偏应用层的编程语言中,比如 Java,会屏蔽掉底层的中断细节,对于共享变量的互斥保护转而以更强大、更安全和更灵活的封装函数去代替,比如在 Java 中提供了锁机制,有对象锁和类锁,有 synchronized 关键字,因此接下来的实现代码中对于 PV 操作我们会使用这些机制来保证其原子性,主要还是为了最终的实现效果。

信号量的运用

互斥控制

回到上面提到的互斥问题,当 A 和 B 两个线程同时向 Bank 存入 money 时,由于对 money 不加保护,导致在某些时刻 A 和 B 会同时修改 money 值。现在我们尝试对 money 资源进行 PV 保护。

首先我们实现一个信号量对象 MutualExclusionSemaphore

public class MutualExclusionSemaphore {
    private static int S = 1;

    public MutualExclusionSemaphore(int S) {
        this.S = S;
    }

    public synchronized void P(){
        S--;
        if (S < 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void V(){
        S++;
        if (S <= 0){
            notify();
        }
    }
}

可以看到这里我们定义了一个信号量 S 等于 1,之所以等于 1 是因为共享变量 money 只有一个。

我们使用了 synchronized 关键字来确保 PV 操作的原子性,即当一个线程进行 P 操作时会获得 MutualExclusionSemaphore 对象的对象锁,从而确保不会由于更新 S 值而带来新的互斥问题。

我们使用 wait() 和 notify() 来实现阻塞线程和随机重启线程的步骤。

wait(): Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.

notify(): Wakes up a single thread that is waiting on this object's monitor.

接下来我们需要修改执行线程,加入 PV 操作

public class AddMoneyThread implements Runnable {
    private MutualExclusionSemaphore mutualExclusionSemaphore;
    private Bank bank;
    private int addMoneyCount;
    public int id;

    public AddMoneyThread(Bank bank, int addMoneyCount, int id, MutualExclusionSemaphore mutualExclusionSemaphore) {
        this.bank = bank;
        this.addMoneyCount = addMoneyCount;
        this.id = id;
        this.mutualExclusionSemaphore = mutualExclusionSemaphore;
    }

    @Override
    public void run() {
        if (bank != null) {
            int index = 1;
            while (index <= this.addMoneyCount) {
                mutualExclusionSemaphore.P();
                bank.addMoney();
                index++;
                mutualExclusionSemaphore.V();
            }
        }
    }
}

这里要注意各个线程应该共用同一个 MutualExclusionSemaphore 对象。我们在 addMoney 方法上下加上 PV 操作,然后执行主函数看看输出。

第1次30000
第2次30000
第3次30000
第4次30000
第5次30000

由于 PV 操作的存在,现在不会出现两个线程同时操作 money 变量带来的互斥问题了。当 S 大于等于 0 时,我们可以理解为可用的资源数目,当 S 小于 0 时,我们可以将其理解为正在等待被重启的线程数目。

同步控制

现在我们讨论一下同步问题。我们前面提过,在同步问题中,是由于 save 和 take 之间有一个暗含的同步关系,即只有当仓库中有空闲位时才可以 save,只有当有仓库中存有货物时才可以 take。

save 和 take 操作针对的资源其实是不一样的,save 利用的是空闲位资源,而 take 利用的是存储位资源。所以我们其实应该设置两个信号量,一个表示有空闲位,一个表示有存储位,我们可以分别定义为 idle 和 used。初始状态时,由于仓库是空的,所以 idle 等于仓库大小,used 等于 0。

接下来分别考虑 save 和 take 操作。

当我们进行 save 操作时,如果操作成功,则存储位加 1,如果没有空闲位则 save 会被阻塞。

P(idle)

save()

V(used)

当我们进行 take 操作时,如果操作成功,则空闲位加 1,如果没有存储位则 take 会被阻塞。

P(used)

take()

V(used)

接下来我们实现一个信号量对象 SynchronizationSemaphore

public class SynchronizationSemaphore {
    private static int idle = 0;
    private static int used = 0;

    public SynchronizationSemaphore(int idle, int used){
        this.idle = idle;
        this.used = used;
    }

    public synchronized void PEmpty(){
        idle--;
        if (idle < 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void VEmpty(){
        idle++;
        if (idle <= 0){
            notify();
        }
    }

    public synchronized void PFull(){
        used--;
        if (used < 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void VFull(){
        used++;
        if (used <= 0){
            notify();
        }
    }
}

然后我们更改 saveGoodThread 和 takeGoodThread 的 run 方法

    /* saveGoodThread */
    @Override
    public void run(){
        if (repository != null){
            int i = 0;
            while (i < this.saveNumber){
                synchronizationSemaphore.PEmpty();
                repository.saveGood(i + 1);
                i++;
                synchronizationSemaphore.VFull();
            }
        }
    }
    /* takeGoodThread */
    @Override
    public void run() {
        while (true) {
            synchronizationSemaphore.PFull();
            Goods temp = repository.takeGood();
            number++;
            if (temp != null){
                goodsList.add(temp);
                synchronizationSemaphore.VEmpty();
            }
        }
    }

那么运行结果怎样呢?

第1次9991
第2次10000
第3次10000
第4次9994
第5次10000

可以看到,第 2、3、5 次都得到了我们期望的结果,可以第一次和第四次表现的依旧不是很好,那么这里的问题出在哪里了呢?

隐藏的互斥问题

其实这个问题很隐蔽,让我们回忆下整个过程,我们虽然用 PV 操作确保了 save 时仓库一定有空闲位,take 时仓库一定有存储位,但是 saveGoodThread 线程和 takeGoodThread 线程对于仓库的操作都涉及到一个变量 usedSize,正是这个小变量没有被保护,所以带来了一个隐藏的互斥问题,根据上一部分的讲解,其实解决方式也很简单,我们在 SynchronizationSemaphore 中增加一个互斥锁变量 mutex,具体如下

public class SynchronizationSemaphore {
    private static int idle = 0;
    private static int used = 0;
    private static int mutex = 1;

    public SynchronizationSemaphore(int idle, int used){
        this.idle = idle;
        this.used = used;
    }

    public synchronized void PEmpty(){
        idle--;
        if (idle < 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void VEmpty(){
        idle++;
        if (idle <= 0){
            notify();
        }
    }

    public synchronized void PFull(){
        used--;
        if (used < 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void VFull(){
        used++;
        if (used <= 0){
            notify();
        }
    }

    public synchronized void PMutex(){
        mutex--;
        if (mutex < 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public synchronized void VMutex(){
        mutex++;
        if (mutex <= 0){
            notify();
        }
    }
}

然后继续修改 saveGoodThread 和 takeGoodThread 的 run 方法

    /* saveGoodThread */
    @Override
    public void run(){
        if (repository != null){
            int i = 0;
            while (i < this.saveNumber){
                synchronizationSemaphore.PEmpty();
                synchronizationSemaphore.PMutex();
                repository.saveGood(i + 1);
                i++;
                synchronizationSemaphore.VMutex();
                synchronizationSemaphore.VFull();            }
        }
    }
    /* takeGoodThread */
    @Override
    public void run() {
        while (true) {
            synchronizationSemaphore.PFull();
            synchronizationSemaphore.PMutex();
            Goods temp = repository.takeGood();
            number++;
            if (temp != null){
                goodsList.add(temp);
                synchronizationSemaphore.VEmpty();
            }
            synchronizationSemaphore.VMutex();
            }
        }
    }

隐藏的死锁问题

这里我们其实是这样实现 PV 操作的。

当我们进行 save 操作时,如果操作成功,则存储位加 1,如果没有空闲位则 save 会被阻塞。

P(idle)
P(mutex)

save()

V(mutex)
V(used)

当我们进行 take 操作时,如果操作成功,则空闲位加 1,如果没有存储位则 take 会被阻塞。

P(used)
P(mutex)

take()

V(mutex)
V(idle)

这里也有一个问题,对于 mutex 值的操作是否必须要在 used 和 idle 操作之间呢,比如是否可以进行如下顺序操作呢?

P(mutex)
P(idle)

save()

V(used)
V(mutex)

其实这样会引起一个非常严重的问题,即 死锁 ,死锁是指一组线程都被阻塞了,并等待一个永远不为真的条件。死锁是一个相当困难的问题,因为它总是不能预测,也许代码可以运行上万小时都不会出错,但接下来一分钟就发生了死锁。

而在这里,我们可以分析一种情况,如果 saveGoodThread 先通过 P(mutex) 操作获得了对 Repository 的资源锁,这时 takeGoodThread 将会在 P(mutex) 阻塞,但 saveGoodThread 接下来发现 Repostitory 的数组满了,所以它会阻塞并等待 takeGoodThread 释放一些空闲位,而 takeGoodThread 此时已经没有办法被重启了。同理也可以推出其他几种死锁情况。

现在让我们运行下主程序。

第1次10000
第2次10000
第3次10000
第4次10000
第5次10000

这一次完美获得了期望结果。

结尾

可以看到互斥问题和同步问题其实经常是交织在一起的,比如经典的"生产者与消费者问题",并发编程由于其调试的困难和运行流程的不可预测性而给开发者带来了很多挑战,但同时良好的并发编程又能大大提升代码运行效率和计算资源的利用率。

源码地址

本帖已被设为精华帖!
共收到 5 条回复
5733 1506307741

不错 学习了

96
mm0089 · #2 ·

臺灣約妹找妓女做愛打炮賴mm0089 堅持讓你花得值得 讓你一次就滿意
薇薇外送茶優質正妹網站:http://www.kiss69lg.com/forum.php?forumlist=1&mobile=2
堅持讓你花得值得 讓你一次就滿意
保證咩咩 熟練多變的技巧 讓你三度回味
堅持誠信經營 絕不會說的天花亂墬 浪費彼此寶貴的時間
精心安排 保證約好的時間 20分鐘內火速到達
絕無強迫消費‧購買點數‧匯款‧ATM轉帳‧都是見到本人 滿意在消費
優咩咩都是經過篩選 絕無地雷 請放心光臨體驗享受
㊣平價錢又安心 熟客 新客都享有優惠
薇薇茶坊營業時間:中午12點-凌晨四點

外送地區:臺北新北林口龜山新竹臺中彰化南投高雄臺南
妹妹類型:學生 OL 巨乳 蘿莉 人妻 技術茶 空姐小模 AV女優 婚紗助理等
服務內容:全套服務
約妹流程:賴上先預約然後準時給房號在房間等妹到
約會地點:你自己選擇的旅館or熟客可送住家
消費方式:看到妹喜歡在當場現金交易 不喜歡可換三次

北部:一節6000內立減500送1000優惠券
一節7000 -8000第二節半價 買三節送一節
一節9000-10000 立減1000-2000送2次半價
一節11000-15000 立減3000-5000送3次半價
一節15000-30000 立減5000-8000 送一年半價

中南部:一節4000-5000 二節半價
一節6000-7000 買兩節送一節
一節7000-10000 買兩節送兩節
一節11000-15000 立減3000-5000送2次半價
一節15000-30000立減5000-8000送一年半價
約小姐部落格看照網址:https://www.cssanyu.org/bbs2/forum.php?mod=viewthread&tid=333726&extra=
約小姐部落格看照https://www.photostore.me/mm0089/?list=images&sort=date_desc&page=4&seek=ursjV
雙北桃園林口龜山新竹看照約妹網址:http://www.kiss69lg.com/forum.php?mod=forumdisplay&fid=143
臺中彰化南投看照約妹網址:http://www.kiss69lg.com/forum.php?mod=forumdisplay&fid=145
高雄臺南看照約妹網址:http://www.kiss69lg.com/forum.php?mod=forumdisplay&fid=147
安全旅館便宜經濟實惠旅館推薦:http://www.kiss69lg.com/forum.php?mod=forumdisplay&fid=182
色情圖片露點照片網址:http://www.kiss69lg.com/forum.php?mod=forumdisplay&fid=177
成人小說網址http://www.kiss69lg.com/forum.php?mod=forumdisplay&fid=163
台中台北外送茶莊、茶坊推薦|優質台北、高雄外約茶妹任你選南投外送茶/全套叫小姐彰化外送茶
草屯外送茶大台中外送茶,外約美女,薇薇外送茶賴mm0089外約服務網,台中一夜情 .台灣出差旅館叫小姐line:mm0089
台北外送茶/台中外送茶/高雄外送茶/台南外送茶/新竹外送茶/彰化外送茶/南投外送茶/薇薇外送茶賴mm0089大台灣台北台中高雄台南新竹地區喝茶服務外送茶薇薇外送茶賴mm0089/鼓山區看照約妹薇薇外送茶賴mm0089前鎮區美腿茶/三民區火辣茶/新興區惹火嫵媚茶/左營區MT外送... 外送/台北清涼茶莊/更多優質妹妹/台北外送茶莊/高雄外送茶/新竹外送茶/洗澡/愛愛薇薇外送茶賴mm0089/口交旅館酒店外送小姐/上門服務/薇薇外送茶賴mm0089茶魚分享/大台北外送茶坊/大台中外送茶坊/高雄外送茶/援交妹網站台灣叫小姐俱樂部,薇薇外送茶賴mm0089台北叫小姐,薇薇外送茶賴mm0089西門町外送服務 板橋外送茶高雄約妹外約茶莊薇薇外送茶賴mm0089/夜市附近叫小姐,85大樓叫小姐 高雄叫小姐按摩外送茶西?町找小姐薇薇外送茶賴mm0089林森北找茶喝/台北?正妹/台?援交找薇薇外送茶賴mm0089台北叫小姐台中旅館叫小姐高雄旅遊找妹兼職美女茶外送看照約妹好茶外送到家西?町找小姐|林森北找茶喝/台北?正妹/台?援交找薇薇外送茶賴mm0089
#台灣汽車旅館找小姐 #商旅找小姐薇薇外送茶賴mm0089 #星級酒店找小姐
#旅館找小姐 #商旅找小姐薇薇外送茶賴mm0089 #星級酒店找小姐 #旅館找小姐
#飯店找小姐 #住家找小姐薇薇外送茶賴mm0089 #賓館找小姐 #台灣出差旅遊外約 #台灣出差旅遊找小姐
#酒店外約叫茶 #台灣汽車旅館外約叫茶 薇薇外送茶賴mm0089#商旅外約叫茶 #星級酒店外約叫茶 #旅館外約叫茶 #飯店外約叫茶
#住家外約叫茶 #賓館外約叫茶 #台灣出差旅遊外約叫茶 薇薇外送茶賴mm0089#台灣出差旅遊外約叫茶 +薇薇外送茶賴mm0089#
台中外送茶 #台北外送茶 #新竹外送茶薇薇外送茶賴mm0089 #高雄外送茶 #台南外送茶 #彰化外送茶 #南投外送茶 #台中外約
#台北外約 #高雄外約薇薇外送茶賴mm0089 #新竹外約 #台南外約 #彰化外約 #南投外約 #台灣外送茶 #外送茶 #情愛全台外送茶看照約妹叫小姐
#外送茶不戴套 +薇薇外送茶賴mm0089#板橋外約 #三重外約 #永和外約 #中和外約 #汐止外約 #新莊外約 #土城外約
#新店外約 #蘆洲外約 #五股外約 #泰山外約 #淡水外約薇薇外送茶賴mm0089 #八里外約 #林口外約 #龜山外約 #台中外約 #高雄外約
#台北外約薇薇外送茶賴mm0089 #本土外約 #外約台妹 #中正外約 #大同外約 #松山外約+薇薇外送茶賴mm0089 #板橋外送茶 #板橋外約
#大安外約 #萬華外約 #信義外約薇薇外送茶賴mm0089 #士林外約 #北投外約 #內湖外約 #南港外約 #文山外約 #新竹外約 #台南外約
#西屯外約 #南屯外約 薇薇外送茶賴mm0089#北屯外約 #逢甲外約 #大里外約 #大雅外約 #七其外約 #東海外約 #烏日外約 #太平外約
#豐原外約薇薇外送茶賴mm0089 #沙鹿外約 薇薇外送茶賴mm0089#逢甲茶莊 #逢甲全套
#薇薇外送茶賴mm0089逢甲外約 #逢甲外送茶 #逢甲叫小姐 #逢甲打砲 屏東汽車旅館叫小姐.薇薇外送茶賴mm0089屏東找茶,屏東外送舒壓按摩.屏東護膚全套外約.屏東找妹薇薇外送茶賴mm0089.屏東找小姐.屏東汽車旅館叫妹妹服務薇薇外送茶賴mm0089.屏東叫小姐.妹妹服務找歡樂.薇薇外送茶賴mm0089屏東優質外送茶莊.屏東出差旅遊約妹 .薇薇外送茶賴mm0089屏東正妹論壇.屏東約情人.薇薇外送茶賴mm0089屏東找女人兼職妹.推薦屏東茶莊##屏東外約妹妹價位#屏東找小姐.薇薇外送茶賴mm0089屏東全套外送.屏東辣妹薇薇外送茶賴mm0089.屏東找學生妹.人妻
#逢甲茶訊 #逢甲援交 #逢甲找女人薇薇外送茶賴mm0089 #逢甲魚訊 #逢甲炮神器 #逢甲紓壓 #逢甲性愛服務 #逢甲鐘點情人 #
太平全套 #薇薇外送茶賴mm0089大里全套 #沙鹿全套 #豐原全套 #大雅全套 #烏日全套薇薇外送茶賴mm0089 #台中車站應召 #台中南屯約妹
#台中西屯叫小姐 #台中逢甲外送 #台中勤美約妹薇薇外送茶賴mm0089 #台中車站叫小姐 #台中北屯應召 #台中南屯叫雞 #台中車站叫妹
#台中北區叫小姐薇薇外送茶賴mm0089 #台中應召 #台中車站叫雞 #台中車站茶莊 #台中西屯外送薇薇外送茶賴mm0089 #台中逢甲約砲 #台中北屯叫妹 #台中市區應召桃園半套店薇薇外送茶賴mm0089 ,#桃園半套價錢 #桃園找援,#桃園聯天室找援,#桃園西門找援桃園半套店薇薇外送茶賴mm0089 ,#桃園半套價錢 台中一夜情,台中??,台中全套基隆叫小姐電話 #基隆叫小姐 #基隆飯店叫小姐 #基隆旅館叫小姐薇薇外送茶賴mm0089 #基隆找女人 #基隆找妹 #基隆出差叫小姐 #基隆打炮薇薇外送茶賴mm0089 #基隆看照約妹#桃園茶莊心得,#桃園茶莊ptt,#桃園桑拿薇薇外送茶賴mm0089 ,#桃園桑拿浴,#桃園桑拿網
#桃園桑拿論壇薇薇外送茶賴mm0089 ,#桃園桑拿澳門,#桃園桑拿168,#桃園半套店薇薇外送茶賴mm0089 ,#桃園半套價錢
台北外送茶/高雄鐘點情人外約,台北旅館叫小姐,台北鐘點情人,台灣一夜情,高雄一夜情,台中一夜情,台北一夜情,台北美女外約,台中美女外約,高雄美女外約,高雄茶莊,台中茶莊,台北茶莊,台北叫小姐,台中叫小姐,高雄叫小姐,高雄外約/外約/援交妹,吃魚喝茶論壇,大家來找茶,PLUS,伊利,微克成人網/女優GoGoGo/淘A片/一刀未剪/免費成人影音薇薇外送茶賴mm0089 /交換網站/交友網站/性愛成人網/一葉晴成人貼片/成人極品情色站/癡漢線上免費A片/台灣明星淫片流出露比線上免費A片/伊莉成人論壇/◆免費線上A片◆/寶貝一夜情聊天室/免費A片頻道/無名成人網/成人網/台北情色聯盟/天天幹貼圖/洪爺色情網/台灣噴精成人網/十八小妹自拍美少女自拍貼圖老婆自拍貼圖/色色女孩情色總站/A圖情色交流/666人氣貼圖/插插穴排行/69Kiss電影排行/薇薇外送茶賴mm0089 彩虹頻道/后宮電影院/中文搜性網/酷站排行入口/上我人妻/成人龍虎豹/干爹情色排行/十七歲少女/台灣1歲/104寫真銀行/插插穴排行/熱酷美眉網/台灣性樂園/只有貼圖/波波美女網/交換連結eyny,玩美情人,男人幫,高雄女外約,高雄外送,高雄賓館叫小姐,高雄飯店叫小姐,高雄鐘點情人外約,台北旅館叫小姐,彰化外送茶,台灣兼職美女外送,薇薇外送茶賴mm0089 台北兼職美女外約,台中外送茶坊,高雄外送茶坊,台北外送茶坊陸妹價格,檳榔西施清涼秀,台功援學生兼差 msn,援交妹,24h 台北私兼,CLUB,台南指壓 3k,高雄酒店經紀,台中學生兼差msn,台北 夜店 舞廳 酒吧 制服便服,中年夫妻聯誼,高雄媛交,台南茶妹,台北指油壓留言板,情趣精品,大台南一夜情人外約\俱樂部,台北賓館叫小姐,高雄原味貼身衣物買賣,夢時代購物中心,台北推拿中醫,0204一夜崤◆隆f聊天室,熟女圖,討論區,台南喝茶的店哪好,網路購物,台中理容按摩,台南陪唱,台北下\午茶 blog,高雄 砲友,台中好茶討論區,高雄茶店news,高雄按摩個人工作室,旅遊,台南24h台南24h餐廳,台北茶訊交流msn,卡債,台中旅館外叫服務,台中 spa油壓男按摩小姐服務/漁會玩美情人遊戲成人論壇 台北吃魚喝茶留言板外送/台北一夜情重點情/台北旅館飯店找服務叫小姐/找女人全套服務加按摩指油壓成人夜遊魚訊交流論壇區/台北應召站/伊莉喝茶/第一手論壇/外約愛愛/外約電話/外約高檔茶到府服務/伊莉plus28/成人性愛慾茶園 性交易/正妹外送服務/找茶論壇薇薇外送茶賴mm0089 /找茶討論區/台灣外送GTO/台灣樂緣外送茶/兩性論妹板橋外送酒店 北投泡溫泉三溫暖趙小姐/援交妹網站論壇/FB交友網站/UT天室交友一夜情炮友/台北喝茶買三送一接多買多送純情動感兼職妹/華僑台北旅遊出差消伴遊找女人茶/薇薇外送茶賴mm0089 極品俱樂部嚴選絕色經典,第一手娛樂論壇/卡提諾/玩美情人/吃魚喝茶網 薇薇外送茶賴mm0089 伊漁網/Plus論壇/台灣樂緣/小女人論壇/台灣論壇/微風論壇/伊莉論壇/禁地論壇/維克斯論壇/捷克論/男人幫論壇/大眾論壇/竹北旅館飯店找女人按摩舒壓叫小姐3p服務 愛情公寓論壇 交友/ 愛情/戀愛/貓都論壇/賽斯論壇/104論壇/九州娛樂論壇/櫻雪論壇/2B級/台灣、送茶坊台北外送茶坊,台中外送茶,高雄外送茶,美女外約服務/台中/高雄/新竹/彰化/莊極品俱樂部嚴選絕色經典,成人性愛慾茶園,找茶討論區,催情藥,唯美貼圖,成人論壇,網絡報稅,線上遊戲,高檔平價好茶,淫照聊天是尋夢園美女外送,情趣用品八大行業指油壓全套,暑假打工,網站設計,中國合夥人,鋼鐵俠3,HTC,蝴蝶機,變裝遊戲,茶,motel,hotel,性感絲襪,A片下載,AV女優,第一手論壇,貓都,卡提諾,喝茶,完美情人,BJ論壇,小女人論壇,卡提諾論壇,台灣論壇三溫暖中陪酒ktv,台北全套護膚個人工作室,尋找台南援交auty 美容美體 SPA沙龍,台北外約茶棧,台中越南餐廳,台南應召站,台北外送 3k,高雄下午茶外,台中三溫暖全套,台南全套油壓泰國,台南半套店1600元,高雄推拿指壓,小姐,台北夜生活 pub,台北單身聯誼,兼差,台北交換伴侶,台北一夜情,高雄聊天網,高雄美女兼職,台南車站美食,8000mile,台北一夜情緣俱樂部,高雄應徵\酒店酒店上班,高雄成人視訊聊天室,台南兼職找利菁,高雄24h到府指油壓,女兼職,酒店兼職\,壽山,台南茶訊茶資薇薇外送茶賴mm0089 ,台南陪酒小姐,高雄絲襪美腿高跟鞋,夫妻聯誼部落格,薇薇外送茶賴mm0089 台南小野貓檳榔西施外送時被下藥拍照,自拍女老師,找台中援妹地點,台北大陸妹價格,檳榔西施清涼秀,台功援學生兼差 msn,援交妹,24h 台北私兼,CLUB,台南指壓 3k,高雄酒店經紀,台中學生兼差msn,台北 夜店 舞廳 酒吧 制服便服,中年夫妻聯誼,高雄媛交,台南茶妹,台北指油壓留言板,情趣精品,大台南一夜情人外約\俱樂部,台北賓館叫小姐,高雄原味貼身衣物買賣,夢時代購物中心,台北推拿中醫,0204一夜崤◆隆f聊天室,熟女圖,討論區,台南喝茶的店哪好,網福祿猴林千又 吳宗憲 2015 黑豹旗 王大陸 徐太宇 登革熱 波多野結衣 金鐘獎 蔡英文 洪秀柱 蛇精男 靈異 鬼故事 柯文哲 柯P 大家來說鬼 綜藝玩很大 時尚脈動 賈靜雯 陳佩琪 氣象 反課綱 2015星光大賞 宅男女神 愛爾麗 泛舟哥 張吉吟 ET看電影 八仙 塵爆 楊子晴 范冰冰 安心亞 陳泱瑾 Grace ISIS 林書豪

96
mm685265 · #3 ·

臺灣找小姐+賴:211861 大奶大粉嫩可看照.洗澡愛愛按摩口交全套服務
好吃的東西當然要一起分享~ 小弟昨天趁休假找茶姊約了個正妹 果然幫我安排的素質很讚 妹妹叫童童 目前還是個大學生 22歲 身材臉蛋都是我的菜 身高160 甜美可愛 美腿哦 罩杯Dcup 真材實料 吸起來彈性十足 妹妹雖然年紀小 但是性欲很強 喜歡在床上纏著我的腰扭屁股 真的是視覺上肉體上的100分滿足!!全程真的很主動 很會挑逗 皮膚也很棒很白嫩 全身鮑魚都可以隨意摸哦 全身都很敏感 妹妹也很緊 還會夾我的小弟 插起來水水很多 很有感覺 超級讚!!! 真的是個很淫蕩的小女生 讓你有回味無窮的感覺 喜歡的可以嘗試看看 加賴:211861 找童童可看照片 她家還要其他的姊妹 類型很多 加賴說是阿傑介紹 有好康喔!!!

96
ll211861 · #4 ·

@.全套服務(特別服務可喬)
@.看照約妹~自己看到妹妹滿意現金消費維護客人消費權利~
台灣外送茶可可外送茶【賴211861】【賴211861】專營臺灣本土妹 #情愛全台灣外送茶看照約妹叫小姐 #台灣外送茶 全省外送茶外約援交茶訊魚訊匯總 line茶訊-台灣最大的茶訊、魚訊、外送茶外約網 台灣外送茶價位怎麼算?台灣找小姐上門服務看照約妹會遇雷嗎 台灣找小姐價位多少?看照約妹之台灣酒店找女人上門服務外送茶 援交、魚訊、茶訊、外送茶【賴211861】【賴211861】按摩網站導航 什麼是外送茶- 2020台灣外送茶小貼士 感?茶友的外送茶攻略投稿 - 悅人外送茶 台北外送茶– 茶莊外送茶全套外約叫小姐台灣網友最推薦 外送茶|台北曖昧外送茶即時外約 【賴211861】【賴211861】
台北外送茶彙整- 十八摸外約茶莊-全套-喝茶 【台北推薦】評價最好的5家外送茶!茶莊、價格、外約、到府服務 對味外送茶 讓你有戀愛感覺 台中外約- 叫小姐、外送茶迅速又安全,中部打炮首選- 天使心外約 維珍妮台中茶莊,台中叫小姐及台中外約! - COCO4.5K外送茶 台中外送茶彙整- 十八摸外約茶莊-全套-喝茶 台中女生名單– 糖果外送茶【看照約妹】 台中外送茶 - 激情娛樂外送茶莊 台中外送茶 台中外約學生妹台中一夜情 高雄24小時外約彙整- 十八摸外約茶莊-全套-喝茶 高雄外送茶有著2020茶莊界最好的美眉! 想在高雄叫小姐嗎 #高雄外送茶#高雄看照?妹#高雄叫小姐 高雄外送茶- 最夯E杯大奶妹,擁有傲人的上圍,讓你體驗波濤洶湧 老王-吃魚喝茶術語大全教學 所謂的喝茶就是援交或性交易【賴211861】【賴211861】小資族喝茶吃魚如何少花冤枉錢 萬維論壇 台灣喝茶吃魚可馨蘿莉幼齒學生妹甜美可愛清純粉 [討論] 關於吃魚喝茶心得分享的人- 看板sex - PTT網頁版 台灣喝茶吃魚可馨蘿莉幼齒學生妹甜美可愛清純粉 [討論] 關於吃魚喝茶心得分享的人- 看板sex - PTT網頁版 大台灣出差旅遊找小姐喝茶吃魚喝茶吃魚找小妹貼心 中文色情??,成人??,自拍,偷拍,成人??翻?? 情色網站大全- 成人網站- 色情網 成人貼圖區 - 一世發論壇 全球十大熱門成人網站排名【211861】【賴211861】線上直播網紅模式將成主流 台灣論壇,伊莉論壇區,維克斯論壇,論壇成人版,微風論壇--奇雅網 成人園地 SOGO論壇 夜遊討論- 伊莉討論區 水蜜桃外送茶拉莉塔外送茶海盜外送茶佳佳外送茶妮娜外送茶志玲外送茶小妖精外送茶小騷騷外送茶#台北夜遊論壇 夜遊討論~ 茶魚飯後閒聊版-魚訊交流歐洲論壇神奇論壇東歐個工 小司機帶路西斯板Dcard 夜遊論壇 - 新竹找茶,新竹找魚茶, 桃園中壢OL兼職, LINE 【心得】 夜遊論壇,按摩論壇【休閒娛樂】 魚訊與個工評價討論-歡樂論壇 夜遊討論區-貓都論壇 傑克論壇按摩舒壓理容 武士成人娛樂論壇娛樂網男人碼頭/夜遊討論一世一夜 外約學生妹約茶高檔正妹麻豆藝人小模空姐援交妹學生處女台灣蘿莉送茶吃魚喝茶人妻女優泰洗處女敢玩3P雙飛無套全場3K起【有圖】【賴211861】【賴211861】 #高雄外送茶,#高雄約炮,#高雄外約,#高雄吃魚喝茶,#高雄找小姐,#高雄全套,#高雄茶莊,#高雄茶訊【賴211861】【賴211861】 #高雄約炮神器,#高雄一夜情,#高雄炮友,#高雄買春,#高雄叫雞,#高雄嫖妓,台中外送茶 台中學生 台中一夜情 台中找小姐 台中全套茶旅館愛愛 台中單親媽媽 #高雄找炮友#高雄外送茶 #高雄外送茶,#高雄約炮,#高雄外約 #台北外約 #台北外送茶 #台北買春 #台北叫小姐 #台北叫全套 #台北按摩 #台北半套 #台北找援交妹 #台北茶莊 #台北上門按摩 #台北找茶 #台北外約小姐 #台北找一夜情 #台北外約 #台北外送茶 #台北買春 #台北叫小姐 #台北叫全套 #台北按摩 #台北外約 #台北外送茶 #台北買春 #台北叫小姐 #台北叫全套 #台北按摩約炮 #台北找援交妹 #台北茶莊 #台北上門按摩 #台北找茶 #台北外約小姐【賴211861】【賴211861】 #台北找一夜情 #台北外約 #台北外送茶 #台北買春 #台北叫小姐 #台北叫全套 #台北按摩 #台北半套 #台北外約 #台北外送茶 #台北買春 #台北叫小姐 #台北叫全套 #台北按摩 #台北找援交妹 #台北茶莊 #台北上門按摩 #台北找茶 #台北外約小姐 #台北找一夜情 #台北外約 #台北外送茶 #台北買春 #台北叫小姐 #台北叫全套 #台北按摩 外出茶妹臺北買春優質茶莊台灣蘿莉送茶臺灣嫖妓兼職外約臺北喝茶臺中喝茶高雄喝茶【賴211861】【賴211861】外送茶-叫小姐-外約-全套外送服

96
yt5206978 · #5 ·

+賴:5206978 可看照片 洗澡愛愛按摩口交全套服務 狂插 耐幹 奶泡
聽魚友介紹說她超讚 馬上衝來試一下 見面發現本人比照片好看很多啊! 【超驚喜 XD】 人很熱情很會聊天不會冷場 聊天聊到一半 妹子就自己坐到我身上撒嬌要親親 親到受不了 抱著她衝近了浴室洗澡 互相洗右手摟著我脖子 左手撫摸我下面 妹子慢慢從嘴巴親到脖子在慢慢的跪在地上幫我吹 邊吹邊看著我 這感覺真不錯 超會吹 差點就繳械 還好小弟忍住了 把妹子拉了起來 讓她扶著墻 從後面衝刺 幹到妹子 一直喊不要了 受不了 直到第一發結束 我們一起洗澡去了 床上和她一起做前戲 問她可不可以69 妹子同意了 就互相口 鮑魚很有感覺 粉粉的穴穴 不會像之前約的魚下面超黑 xd 妹子被口到受不了一直扭動著腰 流了很多愛液沒幾分鐘自己就顫抖了起來 說自己要去了 太舒服了~ 呻吟也叫的超淫蕩 ~ 等結束後 妹妹也不會馬上洗澡 還會抱著我躺一會 再拉著我一起去洗澡 超有女友fu ~ 做完評價:妹子很不錯 奶有D奶 服務十分 外貿十分 有女友fu 可69 喜歡上位 超會搖 【推薦出來各位大大要珍惜 妹子是短期兼職 】
密碼:160cm/D/47kg23歲
介紹人:阿傑
LINE : 5206978

需要 Sign In 后方可回复, 如果你还没有账号请点击这里 Sign Up