1. 【Java】原子類

        共 30326字,需瀏覽 61分鐘

         ·

        2023-01-07 04:05

        點(diǎn)擊關(guān)注,與你共同成長(zhǎng)!




        【Java】原子類

        前言

        保證線程安全是 Java 并發(fā)編程必須要解決的重要問(wèn)題。Java 從原子性可見(jiàn)性、有序性這三大特性入手,確保多線程的數(shù)據(jù)一致性。

        • 確保線程安全最常見(jiàn)的做法是利用鎖機(jī)制(Lock、sychronized)來(lái)對(duì)共享數(shù)據(jù)做互斥同步,這樣在同一個(gè)時(shí)刻,只有一個(gè)線程可以執(zhí)行某個(gè)方法或者某個(gè)代碼塊,那么操作必然是原子性的,線程安全的。互斥同步最主要的問(wèn)題是線程阻塞和喚醒所帶來(lái)的性能問(wèn)題;
        • volatile 是輕量級(jí)的鎖(自然比普通鎖性能要好),它保證了共享變量在多線程中的可見(jiàn)性,但無(wú)法保證原子性。所以,它只能在一些特定場(chǎng)景下使用;
        • 為了兼顧原子性以及鎖帶來(lái)的性能問(wèn)題,Java 引入了 CAS (主要體現(xiàn)在 Unsafe 類)來(lái)實(shí)現(xiàn)非阻塞同步(也叫樂(lè)觀鎖),并基于 CAS ,提供了一套原子工具類

        原子類

        原子變量類 比鎖的粒度更細(xì),更輕量級(jí),并且對(duì)于在多處理器系統(tǒng)上實(shí)現(xiàn)高性能的并發(fā)代碼來(lái)說(shuō)是非常關(guān)鍵的。原子變量將發(fā)生競(jìng)爭(zhēng)的范圍縮小到單個(gè)變量上。

        原子變量類可以分為 4

        • 基本類型
          • AtomicBoolean:布爾類型原子類;
          • AtomicInteger:整型原子類;
          • AtomicLong:長(zhǎng)整型原子類。
        • 引用類型
          • AtomicReference:引用類型原子類;
          • AtomicMarkableReference:帶有標(biāo)記位的引用類型原子類;
          • AtomicStampedReference:帶有版本號(hào)的引用類型原子類。
        • 數(shù)組類型
          • AtomicIntegerArray:整形數(shù)組原子類;
          • AtomicLongArray:長(zhǎng)整型數(shù)組原子類;
          • AtomicReferenceArray:引用類型數(shù)組原子類。
        • 屬性更新器類型
          • AtomicIntegerFieldUpdater:整型字段的原子更新器;
          • AtomicLongFieldUpdater:長(zhǎng)整型字段的原子更新器;
          • AtomicReferenceFieldUpdater:原子更新引用類型里的字段。

        基本類型

        • AtomicBoolean:布爾類型原子類;
        • AtomicInteger:整型原子類;
        • AtomicLong:長(zhǎng)整型原子類。

        線程不安全

        package cn.com.codingce.juc.atomic;

        import java.util.concurrent.ExecutorService;
        import java.util.concurrent.Executors;
        import java.util.concurrent.atomic.AtomicInteger;

        public class AtomicIntegerExample {
            
            static int value2 = 0;

            public static void main(String[] args) {
                System.out.println("==============ordinaryMethod==============");
                ExecutorService executor3 = Executors.newFixedThreadPool(5);
                for (int i = 0; i < 10; i++) {
                    executor3.execute(()->{
                        System.out.println(Thread.currentThread().getName() + ", value2: " + value2 + " ");
                        value2++;
                    });
                }
                executor3.shutdown();
            }

        }

        output

        ==============Method==============
        pool-1-thread-3, value2: 0 
        pool-1-thread-2, value2: 0 
        pool-1-thread-3, value2: 2 
        pool-1-thread-4, value2: 0 
        pool-1-thread-1, value2: 0 
        pool-1-thread-3, value2: 3 
        pool-1-thread-2, value2: 2 
        pool-1-thread-5, value2: 1 
        pool-1-thread-4, value2: 5 
        pool-1-thread-1, value2: 5 

        synchronized案例

        package cn.com.codingce.juc.atomic;

        import java.util.concurrent.ExecutorService;
        import java.util.concurrent.Executors;
        import java.util.concurrent.atomic.AtomicInteger;

        public class AtomicIntegerExample {

            static int value1 = 0;

            public static void main(String[] args) {
                System.out.println("==============synchronizedMethod==============");
                ExecutorService executor2 = Executors.newFixedThreadPool(5);
                for (int i = 0; i < 10; i++) {
                    executor2.execute(AtomicIntegerExample::synchronizedMethod);
                }
                executor2.shutdown();
            }

            public static synchronized void synchronizedMethod() {
                System.out.println(Thread.currentThread().getName() + ", value1: " + value1 + " ");
                value1++;
            }

        }

        output

        ==============synchronizedMethod==============
        pool-1-thread-1, value1: 0 
        pool-1-thread-3, value1: 1 
        pool-1-thread-2, value1: 2 
        pool-1-thread-1, value1: 3 
        pool-1-thread-4, value1: 4 
        pool-1-thread-3, value1: 5 
        pool-1-thread-4, value1: 6 
        pool-1-thread-1, value1: 7 
        pool-1-thread-5, value1: 8 
        pool-1-thread-2, value1: 9

        AtomicInteger 案例

        package cn.com.codingce.juc.atomic;

        import java.util.concurrent.ExecutorService;
        import java.util.concurrent.Executors;
        import java.util.concurrent.atomic.AtomicInteger;

        public class AtomicIntegerExample {

            static AtomicInteger value = new AtomicInteger(0);

            public static void main(String[] args) {
                System.out.println("==============AtomicInteger==============");
                ExecutorService executor = Executors.newFixedThreadPool(5);
                for (int i = 0; i < 10; i++) {
                    executor.execute(AtomicIntegerExample::atomicIntegerMethod);
                }
                executor.shutdown();
            }

            public static synchronized void atomicIntegerMethod() {
                System.out.println(Thread.currentThread().getName() + ", value: " + value.get() + " ");
                value.incrementAndGet();
            }

        }

        output

        ==============AtomicInteger==============
        pool-1-thread-2, value: 0 
        pool-1-thread-1, value: 1 
        pool-1-thread-3, value: 2 
        pool-1-thread-5, value: 3 
        pool-1-thread-1, value: 4 
        pool-1-thread-2, value: 5 
        pool-1-thread-4, value: 6 
        pool-1-thread-1, value: 7 
        pool-1-thread-5, value: 8 
        pool-1-thread-3, value: 9

        AtomicInteger實(shí)現(xiàn)可以看到如下定義

        private static final Unsafe unsafe = Unsafe.getUnsafe();
        private static final long valueOffset;

        static {
            try {
                valueOffset = unsafe.objectFieldOffset
                    (AtomicInteger.class.getDeclaredField("value"));
            } catch (Exception ex) { throw new Error(ex); }
        }

        private volatile int value;
        • value - value 屬性使用 volatile 修飾,使得對(duì) value 的修改在并發(fā)環(huán)境下對(duì)所有線程可見(jiàn);
        • valueOffset - value 屬性的偏移量,通過(guò)這個(gè)偏移量可以快速定位到 value 字段,這個(gè)是實(shí)現(xiàn) AtomicInteger 的關(guān)鍵;
        • unsafe - Unsafe 類型的屬性,它為 AtomicInteger 提供了 CAS 操作。

        引用類型

        Java 數(shù)據(jù)類型分為 基本數(shù)據(jù)類型引用數(shù)據(jù)類型 兩大類。

        • AtomicReference:引用類型原子類;
        • AtomicMarkableReference:帶有標(biāo)記位的引用類型原子類;
        • AtomicStampedReference:帶有版本號(hào)的引用類型原子類。

        AtomicStampedReference 類在引用類型原子類中,徹底地解決了 ABA 問(wèn)題,其它的 CAS 能力與另外兩個(gè)類相近,所以最具代表性。

        基于 AtomicReference 實(shí)現(xiàn)一個(gè)簡(jiǎn)單的自旋鎖

        package cn.com.codingce.juc.atomic;

        import java.util.concurrent.ExecutorService;
        import java.util.concurrent.Executors;
        import java.util.concurrent.atomic.AtomicReference;

        public class AtomicReferenceExample {
            public static int ticket = 10;

            public static void main(String[] args) {
                threadSafe();
            }

            private static void threadSafe() {
                SpinLock lock = new SpinLock();
                ExecutorService executorService = Executors.newFixedThreadPool(3);
                for (int i = 0; i < 5; i++) {
                    executorService.execute(new MyThread(lock));
                }
                executorService.shutdown();
            }

            /**
             * 基于 {@link AtomicReference} 實(shí)現(xiàn)的簡(jiǎn)單自旋鎖
             */

            static class SpinLock {
                private AtomicReference<Thread> atomicReference = new AtomicReference<>();

                public void lock() {
                    Thread current = Thread.currentThread();
                    while (!atomicReference.compareAndSet(null, current)) ;
                }

                public void unlock() {
                    Thread current = Thread.currentThread();
                    while (!atomicReference.compareAndSet(current, null)) ;
                }
            }

            static class MyThread implements Runnable {

                private SpinLock lock;

                public MyThread(SpinLock lock) {
                    this.lock = lock;
                }

                @Override
                public void run() {
                    while (ticket > 0) {
                        lock.lock();
                        if (ticket > 0) {
                            System.out.println(Thread.currentThread().getName() + " 賣出了第 " + ticket + " 張票");
                            ticket--;
                        }
                        lock.unlock();
                    }
                }
            }
        }

        output

        pool-1-thread-1 賣出了第 10 張票
        pool-1-thread-3 賣出了第 9 張票
        pool-1-thread-2 賣出了第 8 張票
        pool-1-thread-1 賣出了第 7 張票
        pool-1-thread-2 賣出了第 6 張票
        pool-1-thread-1 賣出了第 5 張票
        pool-1-thread-3 賣出了第 4 張票
        pool-1-thread-1 賣出了第 3 張票
        pool-1-thread-2 賣出了第 2 張票
        pool-1-thread-1 賣出了第 1 張票

        原子類的實(shí)現(xiàn)基于 CAS 機(jī)制,而 CAS 存在 ABA 問(wèn)題,為了解決 ABA 問(wèn)題,才有了 AtomicMarkableReferenceAtomicStampedReference

        AtomicMarkableReference 使用一個(gè)布爾值作為標(biāo)記,修改時(shí)在 true / false 之間切換。這種策略不能根本上解決 ABA 問(wèn)題,但是可以降低 ABA 發(fā)生的幾率。常用于緩存或者狀態(tài)描述這樣的場(chǎng)景。

        package cn.com.codingce.juc.atomic;

        import java.util.concurrent.ExecutorService;
        import java.util.concurrent.Executors;
        import java.util.concurrent.TimeUnit;
        import java.util.concurrent.atomic.AtomicMarkableReference;

        public class AtomicMarkableReferenceExample {

            private final static String INIT_STR = "后端碼匠";

            public static void main(String[] args) throws InterruptedException {

                final AtomicMarkableReference<String> amr = new AtomicMarkableReference<>(INIT_STR, false);

                ExecutorService executorService = Executors.newFixedThreadPool(3);
                for (int i = 0; i < 3; i++) {
                    executorService.execute(() -> {
                        System.out.println("當(dāng)前線程: " + Thread.currentThread().getName());
                        try {
                            Thread.sleep(Math.abs((int) (Math.random() * 100)));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        String name = Thread.currentThread().getName();
                        if (amr.compareAndSet(INIT_STR, name, amr.isMarked(), !amr.isMarked())) {
                            System.out.println(Thread.currentThread().getName() + " 修改了對(duì)象!");
                            System.out.println("新的對(duì)象為:" + amr.getReference());
                        }
                    });
                }

                executorService.shutdown();
                System.out.println(executorService.awaitTermination(3, TimeUnit.SECONDS));
                ;
            }
        }

        output

        當(dāng)前線程: pool-1-thread-1
        當(dāng)前線程: pool-1-thread-3
        當(dāng)前線程: pool-1-thread-2
        pool-1-thread-3 修改了對(duì)象!
        新的對(duì)象為:pool-1-thread-3
        true

        AtomicStampedReference 使用一個(gè)整型值做為版本號(hào),每次更新前先比較版本號(hào),如果一致,才進(jìn)行修改。通過(guò)這種策略,可以根本上解決 ABA 問(wèn)題。

        package cn.com.codingce.juc.atomic;

        import java.util.concurrent.ExecutorService;
        import java.util.concurrent.Executors;
        import java.util.concurrent.TimeUnit;
        import java.util.concurrent.atomic.AtomicStampedReference;

        public class AtomicStampedReferenceExample {

            private final static String INIT_REF = "后端碼匠";
            private final static AtomicStampedReference<String> asr = new AtomicStampedReference<>(INIT_REF, 0);

            public static void main(String[] args) throws InterruptedException {

                System.out.println("初始對(duì)象為:" + asr.getReference());
                ExecutorService executorService = Executors.newFixedThreadPool(3);
                for (int i = 0; i < 3; i++) {
                    executorService.execute(() -> {
                        System.out.println("當(dāng)前線程: " + Thread.currentThread().getName());
                        try {
                            Thread.sleep(Math.abs((int) (Math.random() * 100)));
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                        final int stamp = asr.getStamp();
                        if (asr.compareAndSet(INIT_REF, Thread.currentThread().getName(), stamp, stamp + 1)) {
                            System.out.println(Thread.currentThread().getName() + " 修改了對(duì)象!");
                            System.out.println("新的對(duì)象為:" + asr.getReference());
                        }
                    });
                }

                executorService.shutdown();
                System.out.println(executorService.awaitTermination(3, TimeUnit.SECONDS));
            }

        }

        output

        初始對(duì)象為:后端碼匠
        當(dāng)前線程: pool-1-thread-1
        當(dāng)前線程: pool-1-thread-2
        當(dāng)前線程: pool-1-thread-3
        pool-1-thread-2 修改了對(duì)象!
        新的對(duì)象為:pool-1-thread-2
        true

        數(shù)組類型

        • AtomicIntegerArray:整形數(shù)組原子類;
        • AtomicLongArray:長(zhǎng)整型數(shù)組原子類;
        • AtomicReferenceArray:引用類型數(shù)組原子類。

        AtomicIntegerArray示例

        package cn.com.codingce.juc.atomic;

        import java.util.concurrent.atomic.AtomicIntegerArray;

        public class AtomicIntegerArrayExample {

            private static AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(10);

            public static void main(final String[] arguments) throws InterruptedException {
                System.out.println("Init Values: ");
                for (int i = 0; i < atomicIntegerArray.length(); i++) {
                    atomicIntegerArray.set(i, i);
                    System.out.print(atomicIntegerArray.get(i) + " ");
                }
                System.out.println();
                System.out.println("========================================");
                Thread t1 = new Thread(new Increment());
                Thread t2 = new Thread(new Compare());
                t1.start();
                t2.start();

                t1.join();
                t2.join();

                System.out.println("Final Values: ");
                for (int i = 0; i < atomicIntegerArray.length(); i++) {
                    System.out.print(atomicIntegerArray.get(i) + " ");
                }
                System.out.println();
            }

            static class Increment implements Runnable {
                @Override
                public void run() {
                    for (int i = 0; i < atomicIntegerArray.length(); i++) {
                        int value = atomicIntegerArray.incrementAndGet(i);
                        System.out.println(Thread.currentThread().getName() + ", index = " + i + ", value = " + value);
                    }
                }
            }

            static class Compare implements Runnable {
                @Override
                public void run() {
                    for (int i = 0; i < atomicIntegerArray.length(); i++) {
                        boolean swapped = atomicIntegerArray.compareAndSet(i, 1100);
                        if (swapped) {
                            System.out.println(Thread.currentThread().getName() + " swapped, index = " + i + ", value = 3");
                        }
                    }
                }
            }

        }

        output

        Init Values: 
        0 1 2 3 4 5 6 7 8 9 
        ========================================
        Thread-0, index = 0, value = 1
        Thread-0, index = 1, value = 2
        Thread-1 swapped, index = 0, value = 3
        Thread-0, index = 2, value = 3
        Thread-0, index = 3, value = 4
        Thread-0, index = 4, value = 5
        Thread-0, index = 5, value = 6
        Thread-0, index = 6, value = 7
        Thread-0, index = 7, value = 8
        Thread-0, index = 8, value = 9
        Thread-0, index = 9, value = 10
        Final Values: 
        100 2 3 4 5 6 7 8 9 10 

        屬性更新器類型

        更新器類支持基于反射機(jī)制的更新字段值的原子操作。

        • AtomicIntegerFieldUpdater:整型字段的原子更新器。
        • AtomicLongFieldUpdater:長(zhǎng)整型字段的原子更新器。
        • AtomicReferenceFieldUpdater:原子更新引用類型里的字段。

        這些類的使用有一定限制:

        • 因?yàn)閷?duì)象的屬性修改類型原子類都是抽象類,所以每次使用都必須使用靜態(tài)方法 newUpdater() 創(chuàng)建一個(gè)更新器,并且需要設(shè)置想要更新的類和屬性;
        • 字段必須是 volatile 類型的;
        • 不能作用于靜態(tài)變量(static);
        • 不能作用于常量(final)。
        package cn.com.codingce.juc.atomic;

        import java.util.concurrent.ExecutorService;
        import java.util.concurrent.Executors;
        import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;

        public class AtomicReferenceFieldUpdaterExample {

            static User user = new User("歡迎關(guān)注");

            static AtomicReferenceFieldUpdater<User, String> updater = AtomicReferenceFieldUpdater.newUpdater(User.classString.class, "name");

            public static void main(String[] args) {
                ExecutorService executorService = Executors.newFixedThreadPool(3);
                for (int i = 0; i < 5; i++) {
                    executorService.execute(new MyThread());
                }
                executorService.shutdown();
            }

            static class MyThread implements Runnable {

                @Override
                public void run() {
                    if (updater.compareAndSet(user, "歡迎關(guān)注""后端碼匠")) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName() + " 已修改 name = " + user.getName());
                    } else {
                        System.out.println(Thread.currentThread().getName() + " 已被其他線程修改");
                    }
                }

            }

            static class User {
                volatile String name;

                public User(String name) {
                    this.name = name;
                }

                public String getName() {
                    return name;
                }

                public User setName(String name) {
                    this.name = name;
                    return this;
                }

            }
        }

        output

        pool-1-thread-2 已被其他線程修改
        pool-1-thread-3 已被其他線程修改
        pool-1-thread-2 已被其他線程修改
        pool-1-thread-3 已被其他線程修改
        pool-1-thread-1 已修改 name = 后端碼匠

        方案對(duì)比

        Atomic|synchronized

        1、背后原理的不同

        原子類:它保證線程安全的原理是利用了 CAS 操作。從這一點(diǎn)上看,雖然原子類和 synchronized 都能保證線程安全,但是其實(shí)現(xiàn)原理是大有不同的;

        synchronized:背后的 monitor 鎖,也就是 synchronized 原理,同步方法和同步代碼塊的背后原理會(huì)有少許差異,但總體思想是一致的:在執(zhí)行同步代碼之前,需要首先獲取到 monitor 鎖,執(zhí)行完畢后,再釋放鎖。2、使用范圍的不同

        原子類:它的使用范圍是比較局限的。因?yàn)橐粋€(gè)原子類僅僅是一個(gè)對(duì)象,不夠靈活;

        synchronized 它的使用范圍要廣泛得多。比如說(shuō) synchronized 既可以修飾一個(gè)方法,又可以修飾一段代碼,相當(dāng)于可以根據(jù)我們的需要,非常靈活地去控制它的應(yīng)用范圍;

        所以僅有少量的場(chǎng)景,例如計(jì)數(shù)器等場(chǎng)景,可以使用原子類。而在其他更多的場(chǎng)景下,如果原子類不適用,那么就可以考慮用 synchronized 來(lái)解決這個(gè)問(wèn)題。

        3、粒度的區(qū)別

        原子類:原子變量的粒度是比較小的,它可以把競(jìng)爭(zhēng)范圍縮小到變量級(jí)別;

        synchronized:通常情況下,synchronized 鎖的粒度都要大于原子變量的粒度;

        如果只把一行代碼用 synchronized 給保護(hù)起來(lái)的話,有一點(diǎn)殺雞焉用牛刀的感覺(jué)。

        4、性能的區(qū)別,同時(shí)也是悲觀鎖和樂(lè)觀鎖的區(qū)別

        因?yàn)?synchronized 是一種典型的悲觀鎖,而原子類恰恰相反,它利用的是樂(lè)觀鎖。所以,在比較 synchronized 和 AtomicInteger 的時(shí)候,其實(shí)也就相當(dāng)于比較了悲觀鎖和樂(lè)觀鎖的區(qū)別;

        從性能上來(lái)考慮的話,悲觀鎖的操作相對(duì)來(lái)講是比較重量級(jí)的。因?yàn)?synchronized 在競(jìng)爭(zhēng)激烈的情況下,會(huì)讓拿不到鎖的線程阻塞,而原子類是永遠(yuǎn)不會(huì)讓線程阻塞的。不過(guò),雖然 synchronized 會(huì)讓線程阻塞,但是這并不代表它的性能就比原子類差;

        悲觀鎖的開(kāi)銷是固定的,也是一勞永逸的。隨著時(shí)間的增加,這種開(kāi)銷并不會(huì)線性增長(zhǎng),而樂(lè)觀鎖雖然在短期內(nèi)的開(kāi)銷不大,但是隨著時(shí)間的增加,它的開(kāi)銷也是逐步上漲的

        所以從性能的角度考慮,它們沒(méi)有一個(gè)孰優(yōu)孰劣的關(guān)系,而是要區(qū)分具體的使用場(chǎng)景。在競(jìng)爭(zhēng)非常激烈的情況下,推薦使用 synchronized;而在競(jìng)爭(zhēng)不激烈的情況下,使用原子類會(huì)得到更好的效果。

        注意

        synchronized 的性能隨著 JDK 的升級(jí),也得到了不斷的優(yōu)化。synchronized 會(huì)從無(wú)鎖升級(jí)到偏向鎖,再升級(jí)到輕量級(jí)鎖,最后才會(huì)升級(jí)到讓線程阻塞的重量級(jí)鎖。因此synchronized 在競(jìng)爭(zhēng)不激烈的情況下,性能也是不錯(cuò)的,不需要“談鎖色變”。

        Unsafe類

        實(shí)際上Atomic包里的類基本都是使用Unsafe實(shí)現(xiàn)的包裝類。也就是上面的原子類實(shí)現(xiàn)過(guò)程中都會(huì)用到Unsafe類。Java中的Unsafe類提供了類似C++手動(dòng)管理內(nèi)存的能力。Unsafe類,全限定名是sun.misc.Unsafe,從名字可以看出來(lái)這個(gè)類對(duì)普通程序員來(lái)說(shuō)是“危險(xiǎn)”的,一般應(yīng)用開(kāi)發(fā)者不會(huì)用到這個(gè)類。Unsafe類是"final"的,不允許繼承。且構(gòu)造函數(shù)是private的,無(wú)法在外部對(duì)其進(jìn)行實(shí)例化。

        Unsafe的典型應(yīng)用

        • 堆外內(nèi)存操作。DirectByteBuffer是Java用于實(shí)現(xiàn)堆外內(nèi)存的一個(gè)重要類,通常用在通信過(guò)程中做緩沖池,如在Netty、MINA等NIO框架中應(yīng)用廣泛;DirectByteBuffer對(duì)于堆外內(nèi)存的創(chuàng)建、 使用、銷毀等邏輯均由Unsafe提供的堆外內(nèi)存API來(lái)實(shí)現(xiàn);
        • ReentrantLock、Atomic等API通過(guò)CAS修改state等等,底層用的也是Unsafe;
        • 線程調(diào)度:如LockSupport.park()和LockSupport.unpark()實(shí)現(xiàn)線程的阻塞和喚醒。而 LockSupport的park、unpark方法實(shí)際是調(diào)用Unsafe的park、unpark方式來(lái)實(shí)現(xiàn);
        • 內(nèi)存屏障,通過(guò)Unsafe的loadFence方法加入一個(gè)內(nèi)存屏障,目的是避免指令重排。



        【Java】線程池梳理

        【C++】const關(guān)鍵字


        以上,便是今天的分享,希望大家喜歡,覺(jué)得內(nèi)容不錯(cuò)的,歡迎「分享」「」或者點(diǎn)擊「在看」支持,謝謝各位。

        瀏覽 45
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 爱逼av| 又粗又硬又爽视频 | 尻逼逼| 美女18禁秘 啪啪免费看 | 国产又粗又黄又爽又硬的视频 |