java并发库java.util.concurrent各类的使用示例

本文详细介绍了Java并发库java.util.concurrent中的关键类和接口,包括ExecutorService、ThreadPoolExecutor、Future、Callable等的使用示例,帮助开发者掌握并发编程技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  1. Executor
package concurrent.executor;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

/**
 * Executors类,提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。
 * public static ExecutorService newFixedThreadPool(int nThreads)
 * 创建固定数目线程的线程池。
 * public static ExecutorService newCachedThreadPool()
 * 创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
 * public static ExecutorService newSingleThreadExecutor()
 * 创建一个单线程化的Executor。
 * public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
 * @author xiaoyang
 *
 */
public class TaskExecutorWebServer {
	
	private static final Executor exec=Executors.newFixedThreadPool(100);

	public static void main(String[] args) throws Exception {
		
		ServerSocket ss=new ServerSocket(8888);
		
		while(true){

		    final	Socket connection= ss.accept();
		    Runnable task=new Runnable() {
				
				@Override
				public void run() {
					//handleRequest(connection);
				}
				
			};
			exec.execute(task);
		}
		
		
	}
}
<span style="font-size: 24px; font-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255);">Executorservice</span>
package concurrent.executorservice;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * ExecutorService扩展了Executor并添加了一些生命周期管理的方法。一个Executor的生命周期有三种状态,运行 ,关闭 ,终止 。Executor创建时处于运行状态。当调用ExecutorService.shutdown()后,处于关闭状态,isShutdown()方法返回true。这时,不应该再想Executor中添加任务,所有已添加的任务执行完毕后,Executor处于终止状态,isTerminated()返回true。
 *如果Executor处于关闭状态,往Executor提交任务会抛出unchecked exception RejectedExecutionException。
 * @author xiaoyang
 *
 */
public class LifecycleWebServer {
	
	private  final ExecutorService exec=Executors.newFixedThreadPool(100);
	
	
	public  void start() throws Exception {
		ServerSocket ss=new ServerSocket(8888);
		
		while(!exec.isShutdown()){
			
		    final	Socket connection= ss.accept();
		    Runnable task=new Runnable() {
				
				@Override
				public void run() {
					//handleRequest(connection);
				}
				
			};
			exec.execute(task);
		}
		
		
	}
	
	public void stop(){
		exec.shutdown();
	}
	
	
	
}

package concurrent.executorservice;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * Future<V>代表一个异步执行的操作,通过get()方法可以获得操作的结果,如果异步操作还没有完成,则,get()会使当前线程阻塞。FutureTask<V>实现了Future<V>和Runable<V>。Callable代表一个有返回值得操作。
 * @author xiaoyang
 */
public class CallableLifecycleWebServer {
	
	private  final ExecutorService exec=Executors.newFixedThreadPool(100);
	
	
	public  void start() throws Exception {
		ServerSocket ss=new ServerSocket(8888);
		
		while(!exec.isShutdown()){
			
		    final	Socket connection= ss.accept();
		    Callable<Object> task=new Callable<Object>() {
				
				@Override
				public Object call() {
					//return handleRequest(connection);
					return null;
				}
				
			};
			Future<Object> f= exec.submit(task);
			Object result= f.get();
		}
		
		
	}
	
	public void stop(){
		exec.shutdown();
	}
	
	
	
}

ExecutorThreadPool
package concurrent.ExecutorThreadPool;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler)  

 *corePoolSize:池中所保存的线程数,包括空闲线程(非最大同时干活的线程数)。如果池中线程数多于 corePoolSize,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止。
 
 *maximumPoolSize:线程池中最大线程数
 
 *keepAliveTime:线程空闲回收的时间
 
 *unit:keepAliveTime的单位
 
 *workQueue:保存任务的队列,可以如下选择:
 
 * •  无界队列: new LinkedBlockingQueue<Runnable>();
 * •  有界队列: new ArrayBlockingQueue<Runnable>(8);你不想让客户端无限的请求吃光你的CPU和内存吧,那就用有界队列
 
 *handler:当提交任务数大于队列size会抛出RejectedExecutionException,可选的值为:
 
 * •ThreadPoolExecutor.CallerRunsPolicy 等待队列空闲
 *•ThreadPoolExecutor.DiscardPolicy:丢弃要插入队列的任务
 *•ThreadPoolExecutor.DiscardOldestPolicy:删除队头的任务
 * 
 * @author xiaoyang
 *
 */
public class ExecutorThreadPoolDemo_ {

	public static void main(String[] args) {
		BlockingQueue<Runnable> queue = new LinkedBlockingQueue<Runnable>();
		ThreadPoolExecutor executor = new ThreadPoolExecutor(3, 6, 1, TimeUnit.DAYS, queue);

		for (int i = 0; i < 20; i++) {
			final int index = i;
			executor.submit(new Runnable() {
				public void run() {
					try {
						Thread.sleep(4000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(String.format("thread %d finished", index));
				}
			});
		}
		executor.shutdown();
	}
}

SchedualExecutorService
package concurrent.schedual_executor_service;

import java.util.concurrent.Callable;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
/**
 * ScheduledExecutorService接口
 *在ExecutorService的基础上,ScheduledExecutorService提供了按时间安排执行任务的功能,它提供的方法主要有:
 *•schedule(task,initDelay):安排所提交的Callable或Runnable任务在initDelay指定的时间后执行。
 *•scheduleAtFixedRate():安排所提交的Runnable任务按指定的间隔重复执行
 *•scheduleWithFixedDelay():安排所提交的Runnable任务在每次执行完后,等待delay所指定的时间后重复执行。 
 *@author xiaoyang
*/
public class ScheduledExecutorServiceTest {
	
	private static final ScheduledExecutorService service=Executors.newScheduledThreadPool(2);
	
	public static void main(String[] args) throws Exception {
		
		Runnable task1=new Runnable() {
			
			@Override
			public void run() {
					System.out.println("task1 running ");
			}
		};
		
		@SuppressWarnings("rawtypes")
		final ScheduledFuture future1=service.scheduleAtFixedRate(task1, 1, 10, TimeUnit.SECONDS);
		
		final ScheduledFuture<String> future2=service.schedule(new Callable<String>() {

			@Override
			public String  call() throws Exception {
				  future1.cancel(true);
                  return "task1 cancelled!";
			}
		}, 20, TimeUnit.SECONDS);
		
		System.out.println(future2.get());
		
		service.shutdown();
	}
	
}

CompletionService
package concurrent.CompletionService;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @author xiaoyang
 *
 */
public class CompletionServiceTest {
	public static void main(String[] args) throws Exception {
		CompletionServiceTest t = new CompletionServiceTest();
		t.count1();
		t.count2();
	}
	//使用阻塞容器保存每次Executor处理的结果,在后面进行统一处理
	public void count1() throws Exception{
		long time1=System.currentTimeMillis();
		ExecutorService exec = Executors.newCachedThreadPool();
		BlockingQueue<Future<Integer>> queue = new LinkedBlockingQueue<Future<Integer>>();
		for(int i=0; i<100; i++){
			Future<Integer> future =exec.submit(getTask());
			queue.add(future);
		}
		int sum = 0;
		int queueSize = queue.size();
		for(int i=0; i<queueSize; i++){
			sum += queue.take().get();
		}
		long time2=System.currentTimeMillis();
		System.out.println("总数为:"+sum);
		System.out.println("共耗时:"+(time2-time1));
		exec.shutdown();
	}
	//使用CompletionService(完成服务)保持Executor处理的结果
	public void count2() throws InterruptedException, ExecutionException{
		long time1=System.currentTimeMillis();
		ExecutorService exec = Executors.newCachedThreadPool();
		CompletionService<Integer> execcomp = new ExecutorCompletionService<Integer>(exec);
		for(int i=0; i<100; i++){
			execcomp.submit(getTask());
		}
		int sum = 0;
		for(int i=0; i<100; i++){
			//检索并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。
			Future<Integer> future = execcomp.take();
			sum += future.get();
		}
		long time2=System.currentTimeMillis();
		System.out.println("总数为:"+sum);
		System.out.println("共耗时:"+(time2-time1));
		exec.shutdown();
	}
	//得到一个任务
	public Callable<Integer> getTask(){
		final Random rand = new Random();
		Callable<Integer> task = new Callable<Integer>(){
			@Override
			public Integer call() throws Exception {
				int i = rand.nextInt(10);
				int j = rand.nextInt(10);
				int sum = i*j;
				System.out.print(sum+"\t");
				return sum;
			}
		};
		return task;
	}
	
}

CountDownLatch
package concurrent.CountDownLatch;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
 * 从名字可以看出,CountDownLatch是一个倒数计数的锁,当倒数到0时触发事件,也就是开锁,其他人就可以进入了。
 * 在一些应用场合中,需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发事件,以便进行后面的操作。 
 * CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。
 * 一个CountDouwnLatch实例是不能重复使用的,也就是说它是一次性的,锁一经被打开就不能再关闭使用了,如果想重复使用,请考虑使用CyclicBarrier。
 * 下面的例子简单的说明了CountDownLatch的使用方法,模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。
 * */
public class CountDownLatchTest {

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

		// 开始的倒数锁
		final CountDownLatch begin = new CountDownLatch(1);

		// 结束的倒数锁
		final CountDownLatch end = new CountDownLatch(10);

		// 十名选手
		final ExecutorService exec = Executors.newFixedThreadPool(10);

		for (int index = 0; index < 10; index++) {
			final int NO = index + 1;
			Runnable run = new Runnable() {
				public void run() {
					try {
						begin.await();
						Thread.sleep((long) (Math.random() * 10000));
						System.out.println("No." + NO + " arrived");
					} catch (InterruptedException e) {
					} finally {
						end.countDown();
					}
				}
			};
			exec.submit(run);
		}
		System.out.println("Game Start");
		begin.countDown();
		end.await();
		System.out.println("Game Over");
		exec.shutdown();
	}

}

CyclicBarrier
package concurrent.CyclicBarrier;
import java.io.IOException;
import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** 
* CyclicBarrier类似于CountDownLatch也是个计数器, 
* 不同的是CyclicBarrier数的是调用了CyclicBarrier.await()进入等待的线程数, 
* 当线程数达到了CyclicBarrier初始时规定的数目时,所有进入等待状态的线程被唤醒并继续。 
* CyclicBarrier就象它名字的意思一样,可看成是个障碍, 
* 所有的线程必须到齐后才能一起通过这个障碍。 
* CyclicBarrier初始时还可带一个Runnable的参数, 
* 此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。 
*/
class Runner implements Runnable {

	private CyclicBarrier barrier;

	private String name;

	public Runner(CyclicBarrier barrier, String name) {
		super();
		this.barrier = barrier;
		this.name = name;
	}

	@Override
	public void run() {
		try {
			Thread.sleep(1000 * (new Random()).nextInt(8));
			System.out.println(name + " 准备OK.");
			barrier.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (BrokenBarrierException e) {
			e.printStackTrace();
		}
		System.out.println(name + " Go!!");
	}
}

public class Race {

	public static void main(String[] args) throws IOException, InterruptedException {
		CyclicBarrier barrier = new CyclicBarrier(3);

		ExecutorService executor = Executors.newFixedThreadPool(3);
		executor.submit(new Thread(new Runner(barrier, "zhangsan")));
		executor.submit(new Thread(new Runner(barrier, "lisi")));
		executor.submit(new Thread(new Runner(barrier, "wangwu")));
		
		executor.shutdown();
	}

}

Semaphore
package concurrent.Semaphore;

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

/**
 *  Semaphore当前在多线程环境下被扩放使用,操作系统的信号量是个很重要的概念,
 *  在进程控制方面都有应用。Java 并发库 的Semaphore 可以很轻松完成信号量控制,
 *  Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,
 *  如果没有就等待,而 release() 释放一个许可。
 *  比如在Windows下可以设置共享文件的最大客户端访问个数。 
 *  
 *  Semaphore实现的功能就类似厕所有5个坑,假如有10个人要上厕所,
 *  那么同时只能有多少个人去上厕所呢?同时只能有5个人能够占用,
 *  当5个人中 的任何一个人让开后,其中等待的另外5个人中又有一个人可以占用了。
 *  另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合。
 *
 */
public class SemaphoreSample {
	public static void main(String[] args) {

        // 线程池

        ExecutorService exec = Executors.newCachedThreadPool();

        // 只能5个线程同时访问

        final Semaphore semp = new Semaphore(5);

         // 模拟20个客户端访问

         for (int index = 0; index < 20; index++) {

              final int NO = index;

              Runnable run = new Runnable() {

                 public void run() {

                            try {

                                    // 获取许可

                                    semp.acquire();

                                    System.out.println("Accessing: " + NO);

                                    Thread.sleep((long) (Math.random() * 10000));

                                    // 访问完后,释放

                                    semp.release();

                                    System.out.println("-----------------"+semp.availablePermits());

                            } catch (InterruptedException e) {

                                    e.printStackTrace();

                            }

                  }

              };

              exec.execute(run);

     }

     // 退出线程池

     exec.shutdown();

}
}

Exchanger
package concurrent.Exchanger;

import java.util.concurrent.Exchanger;

/**
 * 类java.util.concurrent.Exchanger提供了一个同步点, 在这个同步点,一对线程可以交换数据。
 * 每个线程通过exchange()方法的入口提供数据给他的伙伴线程, 并接收他的伙伴线程提供的数据,并返回。 当两个线程通过Exchanger交换了对象,
 * 这个交换对于两个线程来说都是安全的。
 */
public class ExchangerSample {
	public static void main(String args[]) {
		Exchanger<String> exgr = new Exchanger<String>();

		new UseString(exgr);
		new MakeString(exgr);
	}
}

class MakeString implements Runnable {
	Exchanger<String> ex;

	String str;

	MakeString(Exchanger<String> c) {
		ex = c;
		str = new String();

		new Thread(this).start();
	}

	public void run() {
		char ch = 'A';
		for (int i = 0; i < 3; i++) {
			for (int j = 0; j < 5; j++)
				str += (char) ch++;

			try {
				str = ex.exchange(str);
			} catch (InterruptedException exc) {
				System.out.println(exc);
			}
		}
	}
}

class UseString implements Runnable {
	Exchanger<String> ex;

	String str;

	UseString(Exchanger<String> c) {
		ex = c;
		new Thread(this).start();
	}

	public void run() {

		for (int i = 0; i < 3; i++) {
			try {
				str = ex.exchange(new String());
				System.out.println("Got: " + str);
			} catch (InterruptedException exc) {
				System.out.println(exc);
			}
		}
	}
}

ReentranLock  Condition

package concurrent.reentranLock_condition;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 *  java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,
 *  它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。
 *  这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。
 *  ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,
 *  但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。
 *  此外,它还提供了在激烈争用情况下更佳的性能。
 *  (换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
*   reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,
*   那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;
*   如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,
*   当线程退出第二个(或者后续) synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。
 */
public class ProductQueue<T> {

    private final T[] items;

    private final Lock lock = new ReentrantLock();

    private Condition notFull = lock.newCondition();

    private Condition notEmpty = lock.newCondition();

    private int head, tail, count;

    public ProductQueue(int maxSize) {
        items = (T[]) new Object[maxSize];
    }

    public ProductQueue() {
        this(10);
    }

    public void put(T t) throws InterruptedException {
        lock.lock();
        try {
            while (count == getCapacity()) {
                notFull.await();
            }
            items[tail] = t;
            if (++tail == getCapacity()) {
                tail = 0;
            }
            ++count;
            notEmpty.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public T take() throws InterruptedException {
        lock.lock();
        try {
            while (count == 0) {
                notEmpty.await();
            }
            T ret = items[head];
            items[head] = null;//GC
            //
            if (++head == getCapacity()) {
                head = 0;
            }
            --count;
            notFull.signalAll();
            return ret;
        } finally {
            lock.unlock();
        }
    }

    public int getCapacity() {
        return items.length;
    }

    public int size() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }

}

package concurrent.reentranLock_condition;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;


public class TestCondition  {
	static final CountDownLatch cdl = new CountDownLatch(31);

	public static void main(String[] args) {
		Monitor monitor = new Monitor();
		ExecutorService executorService = Executors.newFixedThreadPool(31);

		for (int i = 0; i < 30; i++) {
			Student student = new Student(monitor);
			monitor.addClassMate(student);
			executorService.execute(student);
		}
		executorService.execute(monitor);

		System.out.println(Thread.currentThread() + "老师还没来");
		monitor.setTeacherIsComing(true);
		System.out.println(Thread.currentThread() + "老师来了");

		try {
			cdl.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		executorService.shutdown();
	
		
	}
	
	static class Student implements Runnable {
		
		private Monitor monitor;

		Student() {

		}

		Student(Monitor monitor) {
			this.monitor = monitor;
		}

		@Override
		public void run() {

			try {
				monitor.getLock().lock();
				while (!monitor.isTeacherIsComing()) {
					monitor.getCondition().await();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			} finally {
				monitor.getLock().unlock();
				cdl.countDown();
			}
			System.out.println(Thread.currentThread() + "回到自己座位,坐下");
		}
	}

	static class Monitor extends Student {
		
		private boolean teacherIsComing = false;

		private Set<Student> classMates = new HashSet<Student>();

		private Lock lock = new ReentrantLock();

		private Condition condition = lock.newCondition();

		@Override
		public void run() {
			lock.lock();
			while (!teacherIsComing) {
				System.out.println(Thread.currentThread() + "看老师是否来了");
			}
			condition.signalAll();
			lock.unlock();
			cdl.countDown();
		}

		public boolean isTeacherIsComing() {
			return teacherIsComing;
		}

		public void setTeacherIsComing(boolean teacherIsComing) {
			this.teacherIsComing = teacherIsComing;
		}

		public void addClassMate(Student student) {
			classMates.add(student);
		}

		public Condition getCondition() {
			return condition;
		}

		public Lock getLock() {
			return lock;
		}
	}
}

BlockingQueue

package concurrent.blockingQueue.demo;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class TestBlockingQueues {

    public static void main(String[] args) {
        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(20);
        for (int i = 0; i < 10; i++) {
            Thread pro = new Thread(new Producer(queue), "生产者" + i);
            pro.start();
        }
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(new Concumer(queue), "消费者 " + i);
            t.start();
        }

    }

}

class Producer implements Runnable {
    BlockingQueue<String> queue;

    public Producer(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {

        int i = 0;
        while (true) {
            try {
               
                    System.out.println(Thread.currentThread().getName()
                            + "生产食物, 食物编号为:" + Thread.currentThread().getName()
                            + i);
                    queue.put(" 食物 " + Thread.currentThread().getName() + i++);
                    Thread.sleep(10000);
               
            } catch (InterruptedException e) {
                System.out.println("生产者被中断");
            }
        }
    }
}

class Concumer implements Runnable {
    BlockingQueue<String> queue;

    public Concumer(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + " 请求消费");
            try {
                System.out.println(Thread.currentThread().getName() + "消费:"
                        + queue.take());
                Thread.sleep(100);
            } catch (InterruptedException e) {
                System.out.println("消费者被中断");
            }
        }
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值