JUC并发编程学习记录

什么是JUC:java util concurrent 的三个包。

java.util.concurrent

java.util.concurrent.atomic 原子性

java.util.concurrent.lock 锁

分布式 / 集群 

并发、并行

并发编程:多个线程操作同一资源,重复利用cpu的资源

并行:多个人一起行走

线程的状态:

NEW

RUNNABLE

BLOCKED

WATTING

TIMED_WATING

TERMINATED

sleep 和 wait的区别

sleep是Thread类的,wait是Object类的

sleep不释放锁,wait释放锁

sleep可以在任何地方用,wait只能在同步代码块中用

LOCK锁

Synchronized 和 Lock锁的区别

1、synchronized 是内置的一个关键字,Lock是一个类。

2、synchronized是无法判断锁的状态,lock锁可以判断是否获取到了锁

3、synchronized自动释放锁

4、synchronized 线程获取锁后等待,其它线程一直等获取锁的线程释放锁。lock锁的话有一个方法tryLock尝试获取锁

5、synchronized是可重入的不可以中断的,非公平的。Lock可重入,可以判断锁,非公平的(可设置,默认非公平的)。

6、synchronized 适合锁少量的代码块同步问题,Lock锁适合锁大量的同步代码。

锁是什么,如何判断锁的是谁

Syschronized --->wait / notifyAll();   对象

Lock ---->  lock.newCondition()--->await(),signal()

集合类不安全

并发修改异常:java.util.ConcurrentModificationException

 Collections 集合类工具类,可以把线程不安全的集合变安全      

或者用JUC下的类  CopyOnWriteArratList  //写入时复制,COW计算机程序涉及领域的一种优化策略,多个线程调用的时候,list,读取的时候固定的,在写入的时时候避免覆盖,造成数据问题

读写分离。

Hashmap 是线程不安全得,用ConcurrentHashMap<>()代替

Callable

与 Runnable区别,可以有返回值,可以抛出异常,方法不同,run() / call()

结果会被缓存,效率高

get方法可以产生阻塞,放到最后或者使用异步通信来处理。

常用得辅助类

1、CountDownLatch  减法计数器

允许一个或多个线程等待直到在其它线程中执行得一组操作完成的同步辅助。

/**
 * @author fuzucong   计时器 测试
 * @date 2022/1/12 14:40
 */
public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        //总数是6,必须要执行的任务的时候在使用
        CountDownLatch cdl = new CountDownLatch(6);
        for (int i = 0; i <6 ; i++) {
             int a= i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"go out"+a);
                cdl.countDown();  //减一
            },String.valueOf(i)).start();
        }
        cdl.await(); //等待计数器归零,然后再向下执行

        System.out.println("close down");
    }
    
}
cdl.countDown();  //数量减一

cdl.await(); //等待计数器归零,然后再向下执行

每次有线程调用countDown()数量-1,假设计数器数量变为0,cdl.await(); 就会被唤醒,继续执行

2、CyclicBarrier加法计数器

允许一组数据全部等待彼此达到共同屏障点的同步辅助。循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶然等待彼此。屏障被称为循环,因为它可以在等待的线程释放之后重新使用。

/**
 * @author fuzucong 加法计数器
 * @date 2022/1/12 16:53
 */
public class CyclicBarrierTest {

    public static void main(String[] args) {
        //集齐七颗龙珠召唤神龙
        //等7次线程执行完后  执行召唤神龙
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功");
        });

        for (int i = 0; i < 7; i++) {
            new Thread(()-> {
                System.out.println("得到了"+Thread.currentThread().getName()+"颗龙珠。");
                try {
                    cyclicBarrier.await();//等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            },String.valueOf(i)
            ).start();
        }
    }
}

3、Semaphore(信号量)

一个计数信号量,在概念上,信号量维持一组许可证。如果有必要,每个acquire()都会堵塞,直到许可证可用,然后才能使用它。每个release()添加许可证,潜在地释放阻塞获取方。

public class SemaphoreTest {

    public static void main(String[] args) {
        //默认 线程数量 :理解停车位   实用:限流
        Semaphore semaphore = new Semaphore(3);
        for (int i = 0; i < 6; i++) {
            new Thread(()->{
                //每个 acquire() 都会堵塞
                try {
                    semaphore.acquire(); //拿到一个停车位
                    System.out.println(Thread.currentThread().getName()+":抢到车位。");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+":离开车位。");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();   //release()释放
                }
            },String.valueOf(i)).start();
        }
    }
}

semaphore.acquire(); //获得,假设如果满了,其它线程等待,等待被释放为止。
semaphore.release();   //释放,会将当前信号量释放+1,然后唤醒等待的线程。

多个共享资源互斥的使用,并发限流,控制最大的线程数。

读写锁

ReadWriteLoc

package org.threadm;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 独占锁(写锁)一次只能被一个线程占用
 * 共享锁(读锁)多个线程可以同时占用
 * @author fuzucong 读写锁
 * @date 2022/1/12 18:10
 */
public class ReadWriteLockTest {

    public static void main(String[] args) {
        MyCatch myCatch = new MyCatch();
        for (int i = 0; i < 5; i++) {
            final int temp=i;
            new Thread(()->{
                myCatch.put(temp+"",temp+"");
            }).start();
        }


        for (int i = 0; i < 5; i++) {
            final int temp=i;
            new Thread(()->{
                myCatch.get(temp+"");
            }).start();
        }

    }

}
class MyCatch{
    public volatile Map<String,Object> map= new HashMap<>();

    //读写锁:更加细粒度的控制
    public ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    //存  写入的时候 只希望同时只有一个线程写
    public void put(String key,Object value){
        readWriteLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"写入:"+key);
            map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"写入OK");
        } finally {
            readWriteLock.writeLock().unlock();
        }

    }

    //取 读的时候所有人都可以读
    public void get(String key){
        readWriteLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"读取:"+key);
            Object o = map.get(key);
            System.out.println(Thread.currentThread().getName()+"读取OK"+o);
        } finally {
            readWriteLock.readLock().unlock();
        }
    }

}

 队列

 BlockingQueue阻塞队列

 什么情况下使用 阻塞队列:多线程并发处理,线程池!、

四组API

1、抛出异常

2、不会抛出异常

3、阻塞、等待

4、超时等待

方式抛出异常不会抛出异常,有返回值阻塞、等待超时等待
添加addofferputoffer(,,)
移除removepolltakepoll(,,)
检测队列首部elementpeek-
/**
 * @author fuzucong
 * @date 2022/1/13 17:49
 */
public class BlockingQueueTest {
    public static void main(String[] args) throws InterruptedException {
        //throwException();
        noThrowException();
    }
    //抛出异常的方式  添加和移除 超过容量插入和取出会报错   先入先出
    public static void throwException(){
        //设置默认大小为3的队列
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(3);
        System.out.println(arrayBlockingQueue.add("a"));
        System.out.println(arrayBlockingQueue.add("b"));
        System.out.println(arrayBlockingQueue.add("c"));
        System.out.println(arrayBlockingQueue.element());//查看队首元素是谁
        //System.out.println(arrayBlockingQueue.add("c"));  java.lang.IllegalStateException: Queue full

        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        System.out.println(arrayBlockingQueue.remove());
        //System.out.println(arrayBlockingQueue.remove());  java.util.NoSuchElementException
    }

    //不抛出异常,有返回值  不能 插入/取出 后返回一个false/null
    public static void noThrowException() throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(3);
        System.out.println(arrayBlockingQueue.offer("a"));
        System.out.println(arrayBlockingQueue.offer("b"));
        System.out.println(arrayBlockingQueue.offer("c"));
        System.out.println(arrayBlockingQueue.offer("d",2, TimeUnit.SECONDS));  //不抛出异常返回false    堵塞等待两秒

        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        System.out.println(arrayBlockingQueue.poll());
        //System.out.println(arrayBlockingQueue.poll());  不抛出异常返回null

    }

    //不抛出异常,阻塞
    public static void noThrowWaitException() throws InterruptedException {
        ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue(3);
        //一直阻塞
        arrayBlockingQueue.put("a");
        arrayBlockingQueue.put("b");
        arrayBlockingQueue.put("c");
        //arrayBlockingQueue.put("c");  队列没有位置了,一直阻塞


        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        System.out.println(arrayBlockingQueue.take());
        //System.out.println(arrayBlockingQueue.take());  队列没有元素了,一直阻塞

    }


}

SynchronousQueue同步队列

没有容量,不存储元素,进去一个元素,必须等待取出来之后,才能再执行,才能再往里面放一个元素!

/**
 * @author fuzucong
 * @date 2022/1/14 14:24
 */
public class SynchronousQueueTest {
    public static void main(String[] args) {
         SynchronousQueue sq = new SynchronousQueue();
         new Thread(()->{
             try {
                 System.out.println(Thread.currentThread().getName()+" put 1");
                 sq.put("1");
                 System.out.println(Thread.currentThread().getName()+" put 2");
                 sq.put("2");
                 System.out.println(Thread.currentThread().getName()+" put 3");
                 sq.put("3");
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         }).start();

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+"==>"+sq.take());
                System.out.println(Thread.currentThread().getName()+"==>"+sq.take());
                System.out.println(Thread.currentThread().getName()+"==>"+sq.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        
    }
}

线程池

三大方法,7大参数,4种拒绝策略

池化技术

程序运行的本质:占用系统资源。    优化资源的使用--》池化技术

 池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后还给我。

线程池的好处:

1、降低资源消耗。

2、提高响应速度,不用创建和销毁线程。

3、方便管理。 线程复用,可以控制最大并发数,管理线程。

Executors:线程池工具类

/**
 * @author fuzucong
 * @date 2022/1/14 16:13
 */
//使用了线程池之后,要使用线程池创建线程
public class ExecutorsTest {
    public static void main(String[] args) {
        //单个线程
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        //创建一个固定的线程池的大小
        Executors.newFixedThreadPool(5);
        //线程池大小是可伸缩的,
        Executors.newCachedThreadPool();
        try {
            for (int i = 0; i <50; i++) {
                executorService.execute(()->{
                    System.out.println(Thread.currentThread().getName()+"  ok");
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                });
            }

        } finally {
            //线程池用完要关闭,程序结束,关闭线程池
            executorService.shutdown();
        }
    }
}

底层:

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

 public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

综上 代码原理,本质上都是调用的new ThreadPoolExecutor()。

new ThreadPoolExecutor()。

 public ThreadPoolExecutor(int corePoolSize,  //核心线程池大小
                              int maximumPoolSize,//最大核心线程池大小,最大有多少线程数
                              long keepAliveTime,//超时了 没有人调用就会释放
                              TimeUnit unit, //超时单位
                              BlockingQueue<Runnable> workQueue, //阻塞队列
                              ThreadFactory threadFactory,//线程工厂  创建线程的
                              RejectedExecutionHandler handler  //拒绝策略
) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

最大承载:最大线程数量  加  队列数量(max+queue)  如果超过这个数量 --->就会进入拒绝策略

四种拒绝策略

  1. new ThreadPoolExecutor.AbortPolicy() 该拒绝策略是,超过线程数则抛出异常
  2. new ThreadPoolExecutor.CallerRunsPolicy()哪来的到哪去,哪个线程想创建线程,则由创建者去执行。
  3. new ThreadPoolExecutor.DiscardPolicy()队列满了,丢弃任务,不会抛出异常
  4. new ThreadPoolExecutor.DiscardOldestPolicy()队列满了,尝试去和最早的竞争,也不会抛出异常

池的大小怎么设置:

CPU密集型  和  cpu的核数相同就行,

Runtime.getRuntime().availableProcessors()获取cpu逻辑处理器

IO密集型:判断你程序中十分耗IO的线程x,一搬池的大小设置为2x(十分耗io的线程的两倍)。

Stream流式计算

看收藏 --->基础--->java1.8新特性

ForkJoin1.7出现的

分支合并,(在大数据量的时候使用)

并行执行任务,提高效率,大数据量。把大型任务拆分成小任务,然后结果进行合并。

ForkJoin特点:工作窃取

这个里面维护的都是双端队列

package org.forkjoin;

import java.util.concurrent.RecursiveTask;
import java.util.stream.Stream;

/**
 * @author fuzucong   求和计算任务,原理递归
 * @date 2022/1/17 14:41
 */
public class ForkJoinTest1 extends RecursiveTask<Long> {
    private Long start;
    private Long end;

    //临界值
    private Long temp = 100000L;
    public ForkJoinTest1(Long start,Long end){
        this.start=start;
        this.end= end;
    }

    @Override
    protected Long compute() {
        if((end-start)<temp){
            Long sum = 0L;
            for (long i = start; i <=end ; i++) {
                sum+=i;
            }
            return sum;
        }else {
            long middle = (start+end)/2;//中间值
            ForkJoinTest1 forkJoinTest1 = new ForkJoinTest1(start,middle);
            forkJoinTest1.fork(); //拆分任务,把任务压入线程队列
            ForkJoinTest1 forkJoinTest2 = new ForkJoinTest1(middle+1,end);
            forkJoinTest2.fork(); //拆分任务,把任务压入线程队列
            return forkJoinTest1.join()+forkJoinTest2.join();
        }

    }
}

package org.forkjoin;

import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

/**
 * @author fuzucong
 * @date 2022/1/17 15:11
 */
public class TestAdd {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test1();
        test2();
        test3();
    }

    public static void test1() {
        Long sum = 0L;
        long start  = System.currentTimeMillis();
        for (Long i = 1L; i <=10_0000_0000 ; i++) {
            sum+=i;
        }
        long end  = System.currentTimeMillis();
        System.out.println(sum+":时间:"+(end-start));
    }
    public static void test2() throws ExecutionException, InterruptedException {
        //ForkJoin  的方式
        long start  = System.currentTimeMillis();
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> forkJoinTest1 = new ForkJoinTest1(0L,10_0000_0000L);
        ForkJoinTask<Long> submit = forkJoinPool.submit(forkJoinTest1);
        Long sum = submit.get();
        long end  = System.currentTimeMillis();
        System.out.println(sum+":时间:"+(end-start));
    }
    public static void test3() {
        //第三种  用流的方式
        long start  = System.currentTimeMillis();
        //parallel 并行计算
        long sum = LongStream.rangeClosed(0, 10_0000_0000L).parallel().reduce(0, Long::sum);
        long end  = System.currentTimeMillis();
        System.out.println(sum+":时间:"+(end-start));
    }

}

 异步回调

Future设计的初衷:对将来的某个事件的结果进行建模。

public class FutureTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //无返回值的  runAsync
        CompletableFuture<Void> completableFuture= CompletableFuture.runAsync(()->{
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"runAsync=>void");
        });
        completableFuture.get();

        //有返回值的 supplyAsync
        //ajax 成功和失败的回调
        CompletableFuture<String> completableFuture2= CompletableFuture.supplyAsync(()->{
            //int i=1/0;
            return "1024";
        });

        //当编译成功状态
        System.out.println(completableFuture2.whenComplete((t, u) -> {
            System.out.println("t=>" + t); //正常的返回结果
            System.out.println("u=>" + u);   //错误信息java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
        }).exceptionally(u -> {
            //失败返回值
            System.out.println(u.getMessage()); //错误信息java.lang.ArithmeticException: / by zero
            return "233";    //错误返回值
        }).get());

        System.out.println("111111111");
    }

Volatile

volatile是Java虚拟机提供轻量级的同步机制

1、保证可见性

public class VolatileCanSee {
    //不加 volatile 程序就会死循环 ,保证可见性
    volatile static int num = 0;
    public static void main(String[] args) {

        new Thread(()->{
            while (num==0){

            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num=1;
        System.out.println(num);
        
    }
}

2、不保证原子性

        原子性:不可分割

3、禁止指令重排:

指令重排:你写的程序,计算机并不是按照你写的那样去执行的

源代码-->编译器优化的重排-->指令并行也可能回重排-->内存系统也会重排-->执行

处理器在进行指令重排的时候,考虑:数据之间的依赖性

JMM:java内存模型(是一个概念、约定)

1、线程解锁前,必须把共享变量立刻刷到主内存中。

2、线程加锁前,必须读取主存中的最新值到工作内存中。

3、加锁和解锁是同一把锁

内存交互操作又8种

lock 、 unlock 、 read 、load 、use 、assign 、 store 、 write

内存交互操作有8种,速拟机实现必须保证每一个操作都是原子的,不可在分的(对于doubleslong类型的变量来说,load store、read和write操作在某些平台上代许例外)

  •  lock(锁定):作用于主内存的变量, 把一个变量标识为线程独占状态
  • unlock(解锁):作用于主内存的安量,它把一个处于锁定状志的安量释放出来,释放后的变量才可以被其他线程锁定
  • read(读取):作用于主内存安量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load〈载入):作用手工作内存的安量,它把read操作从主存中变量放入工作内存中
  • use(使用):作用于工作内存中的交量,它把工作内存中的安量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令。
  • assign(赋值):作用于工作内存中的交量,它把一个从执行引1繁中接受到的值放入工作内存的变量副本中
  • store(存储):作用于主内存中的交量,它把一个从工作内存中一个变量的值传送到生内存中,以便后续的write使用
  •  write(写入):作用手主内存中的安量,已把store操作从工作内存中得到的安量的值放入主内存的变量中

JMM对这八种指令的使用,制定了如下规则:

  • 工不允许read和load,store和write柳作之一单独出现。即使用了read必须load,使用了store必须write
  • 不允许线程丢弃他最近的assign操作,即工作交量的数据改安了之后,必须告知生存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的交量必须在主内存中诞生,不允许工作内学直接使用一个未被初始化的安量,就是对变量实施use、 store燥作之前必须经过asslgn和load燥作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
  • 如果对一个变量进行lock操作,会清空所有工作内存中此交量的值, 在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
  • 如果一个交量没有被lock,就不能对其进行unlock嫌作,也不能unlock一个被其他线程锁住的变量
  • 对一个交量进行unlock操作之前,必须把此交量同步回生内存

单例模式

public class AverageSingle {

    /**
     * 防止new 对象的时候指令重排
     */
    private volatile static AverageSingle averageSingle;
    private AverageSingle(){

    }

    /**
     *  双层检测锁模式的 懒汉式单例 DCL懒汉式
     */
    public static AverageSingle getAverageSingle(){
        if(averageSingle == null){
            synchronized (AverageSingle.class){
                if(averageSingle == null){
                    averageSingle = new AverageSingle();
                }
            }
        }
        return averageSingle;
    }
}


枚举:

枚举内中没有无参构造,有一个有参构造,源码如下。



深入理解CAS

CAS比较并交换:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作,如果不是就一直循环。

/**
 * @author fuzucong
 * @date 2022/1/20 14:42  深入了解 cas 比较并交换  原子性操作
 */
public class CompareAndSetAtomic {

    public static void main(String[] args) {
        //初始化 大小
        AtomicInteger atomicInteger = new AtomicInteger(10);
        //自增1
        atomicInteger.incrementAndGet();
        //自减1
        atomicInteger.decrementAndGet();

        // expect 期望事什么值   update 如果是期望的值就更新成20
        atomicInteger.compareAndSet(10,20);

        int i = atomicInteger.get();
        System.out.println(i);
        

    }
}

//底层原理自旋锁
public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

ABA问题:

解决:AtomicStampedReference类

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值