Java 线程管理和同步方法汇总

1.Semaphore信号量,所有线程构造的时候共用同一个信号量,在进入同步代码快之前,先获得信号量.示例代码:

package com.cn.sychonized.concurrent;

import java.util.concurrent.Semaphore;

public class SeDemo {

    public static void main(String[] args) {
        /** 允许3个线程同时访问 */
        Semaphore semaphore = new Semaphore(3);

        Person p1 = new Person(semaphore, "A");
        p1.start();
        Person p2 = new Person(semaphore, "B");
        p2.start();
        Person p3 = new Person(semaphore, "C");
        p3.start();
    }

}

class Person extends Thread {

    private Semaphore semaphore;

    public Person(Semaphore semaphore, String name) {
        setName(name);
        this.semaphore = semaphore;
    }

    public void run() {
        System.out.println(getName() + " is waiting ....");
        try {
            /** 获取信号量(获取运行许可证) */
            semaphore.acquire();
            System.out.println(getName() + " is servicing...");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName() + " is done!");
        /** 使用完之后释放,以便其他线程能获取到 */
        semaphore.release();
    }

}

执行结果:

B is waiting ....
C is waiting ....
B is servicing...
C is servicing...
A is waiting ....
A is servicing...
B is done!
A is done!
C is done!

2.synchronized,已经用的不要不要了,锁方法,锁代码块,同一时刻只能有一个线程进入synchronized代码块

    public synchronized void run() {
        System.out.println(getName() + " is waiting ....");
        try {
            System.out.println(getName() + " is servicing...");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName() + " is done!");
    }
    public synchronized void run() {
        System.out.println(getName() + " is waiting ....");
        try {
            System.out.println(getName() + " is servicing...");
            synchronized (semaphore) {
                Thread.sleep(1000);

            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName() + " is done!");
    }

3.CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
主要方法
public CountDownLatch(int count);
public void countDown();
public void await() throws InterruptedException

构造方法参数指定了计数的次数
countDown方法,当前线程调用此方法,则计数减一
await方法,调用此方法会一直阻塞当前线程,直到计时器的值为0

public class CountDownLatchDemo {
    final static SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch=new CountDownLatch(2);//两个工人的协作
        Worker worker1=new Worker("zhang san", 5000, latch);
        Worker worker2=new Worker("li si", 8000, latch);
        worker1.start();//
        worker2.start();//
        latch.await();//等待所有工人完成工作
        System.out.println("all work done at "+sdf.format(new Date()));
    }


    static class Worker extends Thread{
        String workerName; 
        int workTime;
        CountDownLatch latch;
        public Worker(String workerName ,int workTime ,CountDownLatch latch){
             this.workerName=workerName;
             this.workTime=workTime;
             this.latch=latch;
        }
        public void run(){
            System.out.println("Worker "+workerName+" do work begin at "+sdf.format(new Date()));
            doWork();//工作了
            System.out.println("Worker "+workerName+" do work complete at "+sdf.format(new Date()));
            latch.countDown();//工人完成工作,计数器减一

        }

        private void doWork(){
            try {
                Thread.sleep(workTime);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }


}
Worker zhang san do work begin at 2016-03-13 12:14:11
Worker li si do work begin at 2016-03-13 12:14:11
Worker zhang san do work complete at 2016-03-13 12:14:16
Worker li si do work complete at 2016-03-13 12:14:19
all work done at 2016-03-13 12:14:19

CountDownLatch也可以实现当所有任务都创建好以后,统一执行。

4.CyclicBarrier等待所有线程完成工作统一回调,它跟CountDownLatch实现的功能差不多,具体看实例代码

package com.cn.thread;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class MainDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new Runnable() {

            @Override
            public void run() {
                System.out.println("Now All Thread is Complete ");
            }
        });

        new MyThread(cyclicBarrier, "A").start();
        new MyThread(cyclicBarrier, "B").start();
        new MyThread(cyclicBarrier, "C").start();
    }
}

class MyThread extends Thread {
    CyclicBarrier cyclicBarrier;

    public MyThread(CyclicBarrier cyclicBarrier, String name) {
        setName(name);
        this.cyclicBarrier = cyclicBarrier;
    }

    public void run() {
        System.out.println(getName() + " is Started");
        try {
            sleep(1000);
            cyclicBarrier.await();
        } catch (InterruptedException | BrokenBarrierException e) {
            e.printStackTrace();
        }
    };
}

执行结果:

B is Started
A is Started
C is Started
Now All Thread is Complete 

如上所示,所有线程执行完成之后统一回调到CyclicBarrier的Runnable里面。然后在此处做线程执行完之后的处理。

4.Exchanger交换器:Exchanger可以在两个线程之间交换数据,只能是2个线程,他不支持更多的线程之间互换数据。

当线程A调用Exchange对象的exchange()方法后,他会陷入阻塞状态,直到线程B也调用了exchange()方法,然后以线程安全的方式交换数据,之后线程A和B继续运行

package com.cn.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Exchanger;

public class ThreadLocalTest {  

    public static void main(String[] args) {  
        Exchanger<List<Integer>> exchanger = new Exchanger<>();  
        new Consumer(exchanger).start();  
        new Producer(exchanger).start();  
    }  

}  

class Producer extends Thread {  
    List<Integer> list = new ArrayList<>();  
    Exchanger<List<Integer>> exchanger = null;  
    public Producer(Exchanger<List<Integer>> exchanger) {  
        super();  
        this.exchanger = exchanger;  
    }  
    @Override  
    public void run() {  
        Random rand = new Random();  
        for(int i=0; i<10; i++) {  
            list.clear();  
            list.add(rand.nextInt(10000));  
            list.add(rand.nextInt(10000));  
            list.add(rand.nextInt(10000));  
            list.add(rand.nextInt(10000));  
            list.add(rand.nextInt(10000));  

            try {  
                list = exchanger.exchange(list);  

            } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
        }  
    }  
}  

class Consumer extends Thread {  
    List<Integer> list = new ArrayList<>();  
    Exchanger<List<Integer>> exchanger = null;  
    public Consumer(Exchanger<List<Integer>> exchanger) {  
        super();  
        this.exchanger = exchanger;  
    }  
    @Override  
    public void run() {  
        for(int i=0; i<10; i++) {  
            try {  
                list = exchanger.exchange(list);  
            } catch (InterruptedException e) {  
                // TODO Auto-generated catch block  
                e.printStackTrace();  
            }  
            System.out.print(list.get(0)+", ");  
            System.out.print(list.get(1)+", ");  
            System.out.print(list.get(2)+", ");  
            System.out.print(list.get(3)+", ");  
            System.out.println(list.get(4)+", ");  
        }  
    }  
}  

执行结果:

1, 2, 3, 4, 5, 
1, 2, 3, 4, 5, 
1, 2, 3, 4, 5, 
1, 2, 3, 4, 5, 
1, 2, 3, 4, 5, 
1, 2, 3, 4, 5, 
1, 2, 3, 4, 5, 
1, 2, 3, 4, 5, 
1, 2, 3, 4, 5, 
1, 2, 3, 4, 5, 

Producer产生的值在Consumer中打印出来了,Consumer先运行到list = exchanger.exchange(list); 然后等待Producer运行到list = exchanger.exchange(list),然后两者数据交换,继续往下执行。

5.Phaser
Java 7的并发包中推出了Phaser,其功能跟CyclicBarrier和CountDownLatch有些重叠,但是提供了更灵活的用法,例如支持动态调整注册任务的数量等。下面我们介绍一下几个使用场景,看例子大家有明白了。

场景一:我们希望控制多个线程的启动时机:例如在并发相关的单元测试中,有时需要控制线程的启动时机,以期获得最大程度的并发,通常我们会使用CountDownLatch,以下是使用Phaser的版本。

package com.cn.thread;
import java.util.concurrent.Phaser;

public class PhaserTest1 {

    public static void main(String args[]) {
        //
        final int count = 5;
        final Phaser phaser = new Phaser(count);
        for(int i = 0; i < count; i++) {
            System.out.println("starting thread, id: " + i);
            final Thread thread = new Thread(new Task(i, phaser));
            thread.start();
        }
    }

    public static class Task implements Runnable {
        //
        private final int id;
        private final Phaser phaser;

        public Task(int id, Phaser phaser) {
            this.id = id;
            this.phaser = phaser;
        }

        @Override
        public void run() {
            phaser.arriveAndAwaitAdvance();
            System.out.println("in Task.run(), phase: " + phaser.getPhase() + ", id: " + this.id);
        }
    }
}

以上例子中,由于线程是在一个循环中start,因此start的时机有一定的间隔。本例中这些线程实际开始工作的时机是在所有的线程都调用了phaser.arriveAndAwaitAdvance()之后。本例的意思的是等待所有线程都就绪以后,然后再开始统一运行。执行结果如下:

starting thread, id: 0
starting thread, id: 1
starting thread, id: 2
starting thread, id: 3
starting thread, id: 4
in Task.run(), phase: 1, id: 0
in Task.run(), phase: 1, id: 2
in Task.run(), phase: 1, id: 1
in Task.run(), phase: 1, id: 3
in Task.run(), phase: 1, id: 4

等待所有的任务创建好之后,才开始执行run方法里面的内容,达到真正的同步。
如果没有phaser.arriveAndAwaitAdvance();控制,执行结果如下:

starting thread, id: 0
starting thread, id: 1
in Task.run(), phase: 0, id: 0
starting thread, id: 2
starting thread, id: 3
in Task.run(), phase: 0, id: 1
starting thread, id: 4
in Task.run(), phase: 0, id: 2
in Task.run(), phase: 0, id: 3
in Task.run(), phase: 0, id: 4

这样,某些线程已经先于其他线程执行了。

场景二:
有些时候我们希望只有在某些外部条件满足时,才真正开始任务的执行,例如:

package com.cn.thread;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.util.concurrent.Phaser;

public class PhaserTest2 {

    public static void main(String args[]) throws Exception {
        //
        final Phaser phaser = new Phaser(1);
        for(int i = 0; i < 5; i++) {
            phaser.register();
            System.out.println("starting thread, id: " + i);
            final Thread thread = new Thread(new Task(i, phaser));
            thread.start();
        }

        //
        System.out.println("Press ENTER to continue");
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        reader.readLine();
        phaser.arriveAndDeregister();
    }

    public static class Task implements Runnable {
        //
        private final int id;
        private final Phaser phaser;

        public Task(int id, Phaser phaser) {
            this.id = id;
            this.phaser = phaser;
        }

        @Override
        public void run() {
            phaser.arriveAndAwaitAdvance();
            System.out.println("in Task.run(), phase: " + phaser.getPhase() + ", id: " + this.id);
        }
    }
}

所有线程都创建准备好了,等待你发命令,我们就开始跑,这个时候可以初始化一些必要条件然后让他们跑起来,输出结果如下:

starting thread, id: 0
starting thread, id: 1
starting thread, id: 2
starting thread, id: 3
starting thread, id: 4
Press ENTER to continue

in Task.run(), phase: 1, id: 3
in Task.run(), phase: 1, id: 1
in Task.run(), phase: 1, id: 0
in Task.run(), phase: 1, id: 4
in Task.run(), phase: 1, id: 2

场景三:
在Phaser类中,我们在每个线程中,每个线程进行完一个阶段完成后都会等待其他线程完成后再一起进行,当所有线程都完成了一个任务的时候,会调用Phaser的onAdvance方法,如果我们想在每个阶段,所有线程都完成他们的阶段工作后做点啥事的话,那就得继承Phaser类来重写OnAdvance这个方法来实现我们的目的,下面我们用一个例子来说明,例子就是模拟多个学生考试,考试分为三个阶段,每个阶段完成后,都会等待所有的学生完成,同时我们希望在每一个阶段,所有的学生完成一个阶段的任务后打印出几句话,下面看代码。

package com.cn.thread;

import java.util.concurrent.Phaser;

public class PhaserTest3 {

    public static void main(String args[]) throws Exception {
        //
        final int count = 5;
        final int phaseToTerminate = 3;
        final Phaser phaser = new Phaser(count) {
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                System.out.println("====== " + phase + " ======");
                return phase >= phaseToTerminate || registeredParties == 0;
            }
        };

        //
        for(int i = 0; i < count; i++) {
            System.out.println("starting thread, id: " + i);
            final Thread thread = new Thread(new Task(i, phaser));
            thread.start();
        }
    }

    public static class Task implements Runnable {
        //
        private final int id;
        private final Phaser phaser;

        public Task(int id, Phaser phaser) {
            this.id = id;
            this.phaser = phaser;
        }

        @Override
        public void run() {
            do {
                try {
                    Thread.sleep(500);
                } catch(InterruptedException e) {
                    // NOP
                }
                System.out.println("in Task.run(), phase: " + phaser.getPhase() + ", id: " + this.id);
                phaser.arriveAndAwaitAdvance();
            } while(!phaser.isTerminated());
        }
    }
}

先查看执行结果:

starting thread, id: 0
starting thread, id: 1
starting thread, id: 2
starting thread, id: 3
starting thread, id: 4
in Task.run(), phase: 0, id: 3
in Task.run(), phase: 0, id: 1
in Task.run(), phase: 0, id: 0
in Task.run(), phase: 0, id: 4
in Task.run(), phase: 0, id: 2
====== 0 ======
in Task.run(), phase: 1, id: 2
in Task.run(), phase: 1, id: 4
in Task.run(), phase: 1, id: 0
in Task.run(), phase: 1, id: 1
in Task.run(), phase: 1, id: 3
====== 1 ======
in Task.run(), phase: 2, id: 1
in Task.run(), phase: 2, id: 4
in Task.run(), phase: 2, id: 3
in Task.run(), phase: 2, id: 2
in Task.run(), phase: 2, id: 0
====== 2 ======
in Task.run(), phase: 3, id: 4
in Task.run(), phase: 3, id: 0
in Task.run(), phase: 3, id: 3
in Task.run(), phase: 3, id: 1
in Task.run(), phase: 3, id: 2
====== 3 ======

线程非常有序的一个轮回一个轮回的执行,当onAdvance返回false,则认为phaser终止了,while循环结束.

场景四:
在场景三的例子中,主线程在其它工作线程结束之前已经终止。如果希望主线程等待这些工作线程结束,除了使用Thread.join()之外,也可以尝试以下的方式:

package com.cn.thread;

import java.util.concurrent.Phaser;

public class PhaserTest4 {

    public static void main(String args[]) throws Exception {
        //
        final int count = 5;
        final int phaseToTerminate = 3;
        final Phaser phaser = new Phaser(count) {
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                System.out.println("====== " + phase + " ======");
                return phase == phaseToTerminate || registeredParties == 0;
            }
        };

        //
        for(int i = 0; i < count; i++) {
            System.out.println("starting thread, id: " + i);
            final Thread thread = new Thread(new Task(i, phaser));
            thread.start();
        }

        //
        phaser.register();
        while (!phaser.isTerminated()) {
            phaser.arriveAndAwaitAdvance();
        }
        System.out.println("done");
    }

    public static class Task implements Runnable {
        //
        private final int id;
        private final Phaser phaser;

        public Task(int id, Phaser phaser) {
            this.id = id;
            this.phaser = phaser;
        }

        @Override
        public void run() {
            while(!phaser.isTerminated()) {
                try {
                    Thread.sleep(500);
                } catch(InterruptedException e) {
                    // NOP
                }
                System.out.println("in Task.run(), phase: " + phaser.getPhase() + ", id: " + this.id);
                phaser.arriveAndAwaitAdvance();
            }
        }
    }
}

执行结果如下:

starting thread, id: 0
starting thread, id: 1
starting thread, id: 2
starting thread, id: 3
starting thread, id: 4
in Task.run(), phase: 0, id: 0
in Task.run(), phase: 0, id: 3
in Task.run(), phase: 0, id: 1
in Task.run(), phase: 0, id: 2
in Task.run(), phase: 0, id: 4
====== 0 ======
in Task.run(), phase: 1, id: 2
in Task.run(), phase: 1, id: 0
in Task.run(), phase: 1, id: 1
in Task.run(), phase: 1, id: 3
in Task.run(), phase: 1, id: 4
====== 1 ======
in Task.run(), phase: 2, id: 3
in Task.run(), phase: 2, id: 0
in Task.run(), phase: 2, id: 2
in Task.run(), phase: 2, id: 1
in Task.run(), phase: 2, id: 4
====== 2 ======
in Task.run(), phase: 3, id: 0
in Task.run(), phase: 3, id: 2
in Task.run(), phase: 3, id: 1
in Task.run(), phase: 3, id: 3
in Task.run(), phase: 3, id: 4
====== 3 ======
done

场景五:
为了不让一个Phaser关联太多的任务,我们可以采用分而治之的方法,让多个Phaser来组处理这些任务,如下例子:

package com.cn.thread;

import java.util.concurrent.Phaser;

public class PhaserTest6 {
    //
    private static final int TASKS_PER_PHASER = 4;

    public static void main(String args[]) throws Exception {
        //
        final int phaseToTerminate = 3;
        final Phaser phaser = new Phaser() {
            @Override
            protected boolean onAdvance(int phase, int registeredParties) {
                System.out.println("====== " + phase + " ======");
                return phase == phaseToTerminate || registeredParties == 0;
            }
        };

        //
        final Task tasks[] = new Task[10];
        build(tasks, 0, tasks.length, phaser);
        for (int i = 0; i < tasks.length; i++) {
            System.out.println("starting thread, id: " + i);
            final Thread thread = new Thread(tasks[i]);
            thread.start();
        }
    }

    public static void build(Task[] tasks, int lo, int hi, Phaser ph) {
        /**限制每个Phaser只能跑TASKS_PER_PHASER个任务,否则创建新的Phaser关联任务*/
        if (hi - lo > TASKS_PER_PHASER) {
            for (int i = lo; i < hi; i += TASKS_PER_PHASER) {
                int j = Math.min(i + TASKS_PER_PHASER, hi);
                build(tasks, i, j, new Phaser(ph));
            }
        } else {
            for (int i = lo; i < hi; ++i)
                tasks[i] = new Task(i, ph);
        }
    }

    public static class Task implements Runnable {
        //
        private final int id;
        private final Phaser phaser;

        public Task(int id, Phaser phaser) {
            this.id = id;
            this.phaser = phaser;
            this.phaser.register();
        }

        @Override
        public void run() {
            while (!phaser.isTerminated()) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    // NOP
                }
                System.out.println("in Task.run(), phase: " + phaser.getPhase()    + ", id: " + this.id);
                phaser.arriveAndAwaitAdvance();
            }
        }
    }
}

需要注意的是,TASKS_PER_PHASER的值取决于具体的Task实现。对于Task执行时间很短的场景(也就是竞争相对激烈),可以考虑使用较小的TASKS_PER_PHASER值,例如4。反之可以适当增大TASKS_PER_PHASER。

5.执行器管理线程
最常见的线程池

package com.cn.thread;

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

public class ExcutorTest {
     public static void main(String[] args) {
         ExecutorService exes = Executors.newCachedThreadPool();
         for(int i=0; i<5; ++i)
             exes.execute(new MThread());
         exes.shutdown(); 
    }
}

class MThread implements Runnable{
    private static int num = 0;
    @Override
    public void run() {
        while(true){
            synchronized(MThread.class){
                ++num;
                try{
                    Thread.sleep(500);
                } catch(Exception e){
                    System.out.println(e.toString());
                }
                System.out.println(Thread.currentThread().getName() + " " + num);
            }
        }
    }
}

线程池的几种创建模式:
1、newFixedThreadPool创建一个指定工作线程数量的线程池。每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中。
2、newCachedThreadPool创建一个可缓存的线程池。这种类型的线程池特点是:
1).工作线程的创建数量几乎没有限制(其实也有限制的,数目为Interger. MAX_VALUE), 这样可灵活的往线程池中添加线程。
2).如果长时间没有往线程池中提交任务,即如果工作线程空闲了指定的时间(默认为1分钟),则该工作线程将自动终止。终止后,如果你又提交了新的任务,则线程池重新创建一个工作线程。
3、newSingleThreadExecutor创建一个单线程化的Executor,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的 。
4、newScheduleThreadPool创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer。(这种线程池原理暂还没完全了解透彻)

6.实现Callable接口:
Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值,下面来看一个简单的例子:

package com.cn.thread;

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class CallableTest {
    public static void main(String[] args) {
        Callable<String> callable = new Callable<String>() {
            public String call() throws Exception {
                return "Hello World!";
            }
        };
        FutureTask<String> future = new FutureTask<String>(callable);
        new Thread(future).start();
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

执行结果:

Hello World!

FutureTask实现了两个接口,Runnable和Future,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值,那么这个组合的使用有什么好处呢?假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过Future得到。

5.同步锁和原子操作
两种方法都能保证同步

package com.cn.sychonized.concurrent;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LDemo {

    public static void main(String[] args) {
        new MT().start();
        new MT().start();
        new MT().start();
        new MT().start();
    }

}

class Data {

    static int i = 0;
    static Lock lock = new ReentrantLock();
    static AtomicInteger ai = new AtomicInteger(0);

    static void operate() {
        System.out.println(ai.incrementAndGet());
//        lock.lock();
//        i++;
//        System.out.println(i);
//        lock.unlock();
    }

}

class MT extends Thread {
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Data.operate();
        }
    }
}

执行结果:

1
3
2
4
5
7
6
8
9
10
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值