黑马程序员_Java基础_前期准备02-2

---------------------------------------- JavaEE+云物联、期待与您交流!---------------------------------------------

前期准备02-2

 

二、多线程与并发库的应用

①多线程的创建

❶继承Thread类,重写其中的run方法。

❷实现Runnable接口,重写其中的run方法。并将该类的对象,作为参数传递给Thread对象。

❸通过实现Runnable接口,创建线程的方式,更能体现面向对象的编程思想。它将线程本身和被线程执行的代码,进行了单独的封装。

❹若Thread子类既重写了run方法,又接收了实现Runnable接口的对象,将以该Thread子类的run方法作为线程的启动窗口。只有在Thread类的run方法未被重写时,才会去执行Runnable接口的run方法。

❺多线程并不会提高程序的执行效率,并且会降低程序的执行效率,因为CPU的切换会消耗资源。多线程下载能加快速度,是因为抢夺了服务器的带宽。服务器为每一个线程,提供的下载带宽是一定的,多线程下载就是在向服务器提出请求,以获得更多的下载资源。

②定时器(timer['taimə])

❶Timer类

public class Timer

extends Object

1,此类是一种工具,用于安排线程的后台任务。此类的每个对象,对应的是单个后台线程。

2,此类是线程安全的,多个线程可以共享单个Timer对象,而无需进行外部同步。

3,构造方法(部分)

public Timer()

创建一个计时器。此计时器没有守护线程。

4,成员方法(部分)

public void schedule(TimerTask task,long delay)

/*['ʃedju:]安排[ta:sk]任务[di'lei]延迟*/

安排指定的任务,延迟指定的时间后被执行。

public void schedule(TimerTask task,long delay,longperiod)

/*['ʃedju:]安排[ta:sk]任务[di'lei]延迟['piəriəd]周期*/

安排指定的任务,延迟指定的时间后,开始被重复执行,并间遵循指定的周期。如果某一次任务被延迟,其后的执行将被顺延。

❷TimerTask/*[ta:sk]任务*/类

public abstract class TimerTask

extends Object

implements Runnable

1,用来被Timer对象执行的任务。

2,构造方法

protected TimerTask()

创建一个计时器任务。

3,成员方法(部分)

public abstract void run()

此计时器任务要执行的操作。必须覆盖此方法,类似于Thread和Runnable的run方法,用于存放被执行的代码。

4,执行的任务可以被嵌套,在run方法中再创建新的任务。

5,当一个对象的run方法中,嵌套的是同一个类的对象时,将会形成一种类似于递归的操作,创建出大量的对象,直到计时器被取消为止。

6,对于任务的嵌套,实现异步执行的方式有两种:一是为同一个类的对象,设置一个静态的计数器,通过判断计数器的取值,来实现异步执行;二是创建两个不同类的对象,按照不同的间隔执行同样的任务,实现异步执行的目的。

❸Quartz/*[kwɔ:ts]石英*/

Quartz是一个完全由Java编写的开源作业调度框架,能够设置对大量job的调度时间表。

③线程安全

❶多个线程在操作同一份资源时,会造成安全问题。

❷synchronized/*['siŋkrənaizd]同步*/

1,将需要同步的资源被synchronized关键字修饰,使用相同的对象做同步锁,就可以解决多线程的安全隐患。

2,同步代码块,可以用任意相同的对象,就可以完成同步的目的。

3,非静态的同步方法,使用的是this对象,即方法的调用者。

4,静态的同步方法,使用的是该类的字节码文件。

❸同步时的通讯

1,将需要同步的资源,归属在同一个类中,使用不同的方法进行分别封装。这样的设计方式,既可以实现程序高内聚的特性,又可以提高程序的健壮性及可维护性。

2,同步的控制,不应该交给线程,而是交给类中的方法自己实现。这样的好处是,类本身具备了同步性,可以交给任意的线程去执行,而不会发生安全问题。

3,通讯的动作,是通过设置标志位来实现的,同时要配合等待唤醒机制。使用if判断标志位,可以实现双线程间的通讯动作,但是会有伪唤醒的安全隐患。使用while判断标志位,可以实现更多线程的通讯动作,而且能够避免伪唤醒所造成的安全问题。

④ThreadLocal<T>/*['ləukl]本地的*/类

public class ThreadLocal<T>

extends Object

❶此类提供了线程局部变量。在线程执行过程中,将其所操作的数据与该线程进行绑定。实现了不同线程操作同一份资源时,可以保留不同的执行状态。这种执行状态只在本线程范围内共享。

❷构造方法

public ThreadLocal()

创建一个线程本地变量。

❸成员方法

public T get()

返回此线程局部变量,在当前线程副本中的值。

protected T initialValue()

返回此线程局部变量,在当前线程的初始值,默认返回null。如果需要返回其它的初始值,可以创建其子类对象,重写此方法,一般使用匿名内部类的方式。

public void set(T value)

将此线程局部变量,在当前线程副本中的值,设置为指定的值。

public void remove()

移除此线程局部变量,在当前线程上的值。在线程结束之前,应调用此方法擦除记录,释放系统资源。

❹当有多个变量,需要和线程绑定时,有两种实现方式:一是创建多个ThreadLocal对象,每个对象对应一个变量;二是将多个变量封装在一个对象里,把这个对象当做一个变量,传递给一个ThreadLocal对象。将数据封装成对象的方式会更好一些。

❺将数据封装成对象的方法

1,模仿单例设计模式。

2,创建一个拥有单例的类,封装所有需要的数据。

3,将ThreadLocal对象,封装在该类的内部。向ThreadLocal对象中设值时,遵循单例的设计规则。

4,向ThreadLocal对象中设值时,不需要考虑同步的问题,因为每个线程的设值操作互不影响。

5,每个线程都将拥有一个和自己相绑定的对象。

❻ThreadLocal的对象,实际上就是一个Map集合。键是一个线程,值是和该线程绑定的变量。

❼多线程操作共享数据

1,多个线程操作共享的数据,数据的执行状态被多个线程所共享。

2,对共享数据的单向操作(如,卖票),可以使用同一个Runnable对象,将共享数据和对数据的操作行为都封装在该对象中,并且该对象实现了多线程的安全机制。将该对象传递给多个线程调用。思路为操作同一个对象的成员变量。

3,对共享数据的双向操作(有增有减),可以使用某一个对象封装共享数据和对数据的操作行为,该对象实现了多线程的安全机制。在运行时,创建一个Runnable对象,传递给多个相应的线程。两种线程各自调用该对象的不同方法,实现对共享数据的不同操作。思路为调用同一个对象的不同方法,操作该对象的同一个成员变量。

4,对共享数据的双向操作(有增有减),可以使用某一个对象封装共享数据和对数据的操作行为,该对象实现了多线程的安全机制。把两个Runnable对象封装在该对象中,每个Runnable对象实现一种操作行为。使用时将相应的Runnable对象传递给相应的线程。思路为成员内部类调用不同的方法,访问同一个成员变量。

5,对共享数据的互斥操作,封装在同一个对象的不同方法中,更便于操作,更容易实现通讯的动作。

⑤多线程并发库

❶多线程并发库是Java1.5的新特性,保证了多线程的互斥操作。

❷多线程并发库包括三个子包:java.util.concurrent、java.util.concurrent.atomic、java.util.concurrent.locks。

❸java.util.concurrent/*[kən'kʌrənt]并发的*/

此包囊括了对线程池的相关操作,以及一些操作多线程的工具。

❹java.util.concurrent.atomic/*[ə'tɔmik]原子的*/

此包可以对基本数据类型的数据,对数组中基本数据类型的数据,对类中的基本数据类型的数据(即成员变量为基本数据类型的数据)进行操作。

❺java.util.concurrent.locks/*[lɔks]锁*/

此包改善了线程同步及线程间通讯的方式。

❻并发库中的辅助工具

Semaphore/*['seməfɔ:]信号灯*/类

CyclicBarrier/*['saiklik]循环的['bæriə]障碍物*/类

CountDownLatch/*['kauntdaun]倒计时[lætʃ]门闩*/类

Exchanger<V>/*[iks'tʃeindʒə]交换机*/类

TimeUnit枚举

BlockingQueue<E>/*['blɔkiŋ]阻塞[kju:]队列*/接口

ArrayBlockingQueue<E>类

CopyOnWriteArrayList<E>类

CopyOnWriteArraySet<E>类

⑥线程池

❶用于存放线程和任务的容器。将任务投放到线程池中,将被随机分配给某个线程去执行。池中的线程或处于执行状态,或处于等待状态。

❷固定线程池中的线程数量是固定的;缓存线程池中的线程数量是可变的;单一线程池中的线程,会在该线程终止前启动一个替代者;定时器线程池可以使用定时器,来操作线程池中的线程。

❸Executors/*[ig'zekjutəs]执行者*/类

public class Executors

extends Object

1,此类是一个工具类,用于创建一个线程池。

2,成员方法(部分)

public static ExecutorService newFixedThreadPool(intnThreads)

/*[ig'zekjutə]执行者['sə:vis]服务[fikst]固定的[θred]线程[pu:l]池*/

创建一个可重用固定线程数的线程池。

public static ExecutorService newCachedThreadPool()/*[kæʃt]缓存*/

创建一个可根据需要来创建新线程的线程池,但是当以前构造的线程空闲时可重用它们。该线程池可以自动清理,超过60秒未被使用的空闲线程。

public static ExecutorService newSingleThreadExecutor()

创建一个使用单个线程的线程池,以无界队列方式来运行该线程。此方法可以保证顺序的执行各个任务,并且在任意的时间只有一个新的线程在工作。

public static ScheduledExecutorServicenewScheduledThreadPool(int corePoolSize)

/*['ʃedju:ld]已排程的[ig'zekjutə]执行者['sə:vis]服务[pu:l]池[kɔ:l]核心*/

创建一个拥有固定线程数的定时器线程池,它可以安排池中的线程,在给定延迟后或者定期的,执行池中任务。

❹ExecutorService/*[ig'zekjutə]执行者['sə:vis]服务*/接口

public interface ExecutorService

extends Executor

1,此类是普通线程池。当线程池中没有任务等待和被执行时,应当撤销该线程池,以释放资源。

2,成员方法(部分)

public void shutdown()

启动一次顺序关闭,执行以前提交的任务,但不接受新任务。

public List<Runnable> shutdownNow()

试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。

public <T> Future<T> submit(Callable<T>task)

提交一个拥有返回值的任务,并返回一个表示该任务的Future对象。该Future对象的get方法在任务被成功完成时,可以返回该任务的结果。

public Future<?> submit(Runnable task)

/*['fju:tʃə]将来的[səb'mit]提交[ta:sk]任务*/

提交一个Runnable任务用于执行,并返回一个表示该任务的Future对象。该Future对象的get方法在任务被成功完成时,将会返回null。

❺ScheduledExecutorService/*['ʃedju:ld]已排程的[ig'zekjutə]执行者*/接口

public interface ScheduledExecutorService

extends ExecutorService

1,定时器线程池。可以设置线程的执行时间和执行周期。

2,此线程池的定时器只能设置相对时间。如果需要设置绝对时间,可以先算出绝对时间和现在的时间差。

3,成员方法(部分)

public ScheduledFuture<?> scheduled(Runnablecommand,long delay,TimeUnit unit)

/*['ʃedju:ld]已排程的['fju:tʃə]将来的[kə'ma:nd]命令[di'lei]延期['ju:nit]单位*/

接受一个任务,并在指定的时间后执行。

public ScheduledFuture<?>scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnitunit)

/*['ʃedju:ld]已排程的['fju:tʃə]将来的[fikst]固定的[reit]频率[kə'ma:nd]命令[i'niʃəl]初始的[di'lei]延期['piəriəd]周期['ju:nit]单位*/

接受一个任务,并在指定的时间后执行,同时按指定的周期循环执行。

❻Callable<V>/*['kɔ:ləbl]可返回的*/接口

public interface Callable

1,此接口类似于Runnable,但可以返回运行结果。

2,成员方法

public V call()

throws Exception

此方法类似于run方法,可以存放被线程执行的代码,并返回一个结果。如果无法计算出结果,则抛出一个异常。

❼Future<V>接口

public interface Future<V>

1,此接口表示异步计算的结果。

2,成员方法(部分)

public V get()

throws InterruptedException,ExecutionException

/*[intə'rʌptid]中断[eksi'kju:ʃn]执行*/

如有必要,等待计算完成,然后获取其结果。

❽ExecutorCompletionService<V>/*[kəm'pli:ʃn]完成*/类

public class ExecutorCompletionService<V>

extends Object

implements CompletionService<V>

1,此类是具有回收站性质的线程池,可以返回所有任务的结果。

2,构造方法(部分)

public ExecutorCompletionService(Executor executor)

/*[ig'zekjutə]执行者[kəm'pli:ʃn]完成['sə:vis]服务*/

使用指定的线程池,创建一个结果回收站。

3,成员方法(部分)

public Future<V> submit(Callable<V> task)/*['fju:tʃə]将来[səb'mit]提交*/

向结果回收站添加指定的任务,并返回该任务的结果。当向该回收站中添加了多个任务时,结果将会被覆盖返回。

⑧原子数据

❶当多个线程并发访问,被共享的基本数据类型的数据时,将会有并发修改的风险。控制并发访问,要么线程同步,要么共享数据同步,此处实现的是后者。

❷AtomicInteger类

public class AtomicInteger

extends Number

implements Serializable

1,此类可以实现以原子方式更新int值。

2,构造方法(部分)

public AtomicInteger()

创建一个具有给定初始值的,原子整型数。

3,成员方法(部分)

public final int addAndGet(int addend)/*['ædend]加数*/

以原子方式将给定值与当前值相加,并返回被更新的值。

public final int decrementAndGet()/*['dekrimnt]递减*/

以原子方式将当前值减1,并返回被更新的值。

public final int get()

获取当前值。

public final int incrementAndGet()/*['iŋkrimənt]递增*/

以原子的方式将当前值加1,并返回被更新的值。

❸AtomicIntegerArray类

public class AtomicIntegerArray

extends Object

implements Serializable

1,此类可以实现以原子的方式,更新int数组中的元素。

2,构造方法(部分)

public AtomicIntegerArray(int length)

创建一个指定长度的原子数组。

3,成员方法(部分)

public final int addAndGet(int i,int addend)/*['ædend]加数*/

以原子的方式,将指定索引处的值与给定的值相加,并返回被更新的值。

public final int decrementAndGet(int i)/*['dekrimnt]递减*/

以原子的方式,将指定索引处的值减1,并返回被跟新的值。

public final int get(int i)

获取数组中指定位置的当前值。

public final int incrementAndGet(int i)/*['iŋkrimənt]递增*/

以原子的方式,将指定索引处的值加1,并返回被跟新的值。

❹AtomicIntegerFieldUpdater<T>/*[ʌp'deitə]校正器*/类

public abstract class AtomicIntegerFieldUpdater<T>类

extends Object

1,基于反射的工具,可以对指定类的指定int型字段,进行原子更新。

2,成员方法(部分)

public static <U>AtomicIntegerFieldUpdater<U> newUpdater(Class<U> taskClass,StringfieldName)

/*[ə'tɔmik]原子的['intidʒə]整数[fild]字段[ʌp'deitə]校正器[ta:sk]任务*/

为指定的类,使用指定名称的字段,创建并返回一个字段原子校正器。

public int addAndGet(T obj,int addend)/*['ædend]加数*/

以原子的方式,为此校正器所绑定字段,指定对象的值添加指定的值。

public int decrementAndGet(T obj)/*['dekrimnt]递减*/

以原子的方式,将此校正器所绑定字段,指定对象的值减1,并返回被更新的值。

public int getAndSet(T obj,int newValue)

以原子的方式,将此校正器所绑定字段,指定对象的值修改为指定的值,并返回旧值。

public int incrementAndGet(T obj)/*['iŋkrimənt]递增*/

以原子的方式,将此校正器所绑定字段,指定对象的值加1,并返回被更新的值。

⑨线程锁

❶Lock接口

public interface Lock

1,提供了比synchronized更灵活的锁定操作。

2,由于此处的锁是显式调用,需要通过try-finally机制,确保可以释放锁。

3,成员方法(部分)

public void lock()

获取锁。

public Condition newCondition()/*[kən'diʃn]条件*/

获取一个绑定在此锁上的条件对象。

public void unlock()

释放锁。

❷ReentrantLock/*[ri:'entrənt]可重入*/类

public class ReentrantLock

extends Object

implements Lock,Serializable

1,用于创建可重入的锁实例。

2,构造方法()

public ReentrantLock()

创建一个可重入锁实例。

3,成员方法(部分)

public boolean isLocked()

查询此锁是否被任意线程保持。

❸ReadWriteLock接口

public interface ReadWriteLock

1,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥。

2,成员的方法

public Lock readLock()

获取读锁。

public Lock writeLock()

获取写锁。

❹ReentrantReadWriteLock/*[ri:'entrənt]可重入*/类

public class ReentrantReadWriteLock

extends Object

implements ReadWriteLock,Serializable

1,用于创建可重入的读写锁实例。

2,构造方法(部分)

public ReentrantReadWriteLock()

使用默认的(非公平)排序属性,创建一个可重入的读写锁。

3,成员方法(部分)

public ReentrantReadWriteLock.ReadLock readLock()

返回读锁。

public ReentrantReadWriteLock.WriteLock writeLock()

返回写锁。

4,两个静态内部类

public static class ReentrantReadWriteLock.ReadLock

extends Object

implements Lock,Serializable

public static class ReentrantReadWriteLock.WriteLock

extends Object

implements Lock,Serializable

❺缓存系统

1,将数据库中被查询的内容,缓存进内存中,方便再次查找。

2,定义一个Map集合做为代理,查询方式为键,被查询的内容为值。

3,当某一线程提交查询任务时,先在Map中查询,此时上读锁。(若值为null,则释放读锁、上写锁,再次判断值是否为null,若为null,则去数据库中查询,并将结果赋给Map集合的值。释放写锁,获取读锁。)释放读锁,返回值。

❻Condition/*[kən'diʃn]条件*/接口

public interface Condition

1,此接口实现了线程间的通信功能。

2,此接口可以为同一把锁,捆绑多个通信的条件。

3,成员方法(部分)

public void await()

throws InterruptedException/*[intə'rʌptid]中断*/

让当前线程在接到信号,或被中断之前,一直处于等待状态。

public void signal()/*['signl]信号*/

唤醒一个等待线程。

public void signalAll()

唤醒所有等待线程。

⑩并发辅助工具

❶Semaphore/*['seməfɔ:]信号灯*/类

public class Semaphore

extends Object

implements Serializable

1,此信号灯可以理解为通行证,只有持有信号灯的线程,才可以访问资源,其它线程将被阻塞。

2,当信号灯只有一个时,可以实现互斥锁的功能。互斥锁只能是由同一个线程,做获取和释放锁的动作;信号灯则可以是不同的线程,做获取和释放灯的动作。

3,构造方法(部分)

public Semaphore(int permits,boolean fair)/*[feə]公平*/

创建一个具有指定许可数,并且可以是先到先得(true)的信号灯。

4,成员方法(部分)

public void acquire()/*[ə'kwaiə]获取*/

throws InterruptedException

从此信号灯上获取一个通行证。

public void release()/*[ri'li:s]释放*/

从此信号灯上释放一个通信证。

❷CyclicBarrier/*['saiklik]循环的['bæriə]障碍物*/类

public class CyclicBarrier

extends Object

1,此类可以实现对同步的辅助功能。其让一组线程相互等待,直到所有线程都达到后,再继续执行。这就好比一个路障,人到齐了就放开限制,并且此路障可以被重复使用。

2,构造方法(部分)

public CyclicBarrier(int parties,Runnable barrierAction)

/*['saiklik]循环的['bæriə]障碍物['pa:tis]参与者['ækʃn]行为*/

创建一个由指定数量线程参与,并设置了清障反应的障碍物。

3,成员方法(部分)

public int await()

throws InterruptedException,BrokenBarrierException

/*[intə'rʌptid]中断[ig'sepʃn]异常['brəukn]破损的['bæriə]障碍物*/

在所有参与者都到来之前,将一直等待。

❸CountDownLatch/*['kauntdaun]倒计时[lætʃ]门闩*/类

public class CountDownLatch

extends Object

1,此类可以实现对同步的辅助功能。其可以阻拦一组线程,直到倒计时为0。

2,模拟实例,赛跑比赛中,运动员等裁判发令,裁判等运动员全部完成比赛,然后公布比赛的成绩,需要定义两个倒计时器。

3,构造方法

public CountDownLatch(int count)

构造一个倒计时器,并指定起始数字。

4,成员方法(部分)

public void await()

throws InterruptedException

在倒计时结束之前,一直等待。

public void countDown()

让此倒计时器的值递减一次。

❹Exchanger<V>/*[iks'tʃeindʒə]交换机*/类

public class Exchanger<V>

extends Object

1,此类可以实现在两个线程之间交换数据,先到的线程将等待。

2,数据交换的双方,必须使用同一个交换机。

3,构造方法

public Exchanger()

创建一个数据交换机。

4,成员方法

public V exchange(V x)/*[iks'tʃeindʒ]交换*/

throws InterruptedException

将指定的数据交换给调用者,并返回调用者交换过来的数据。

public V exchange(V x,long timeout,TimeUnit unit)

throws InterruptedException,TimeoutException

在限定的时间内,同调用者实现数据的交换。

❺TimeUnit枚举

public enum TimeUnit

extends Enum<TimeUnit>

1,此枚举提供了用于计时的单位,以及一些对时间的使用方法。

2,枚举对象

public static final TimeUnit DAYS

public static final TimeUnit HOURS

public static final TimeUnit MICROSECONDS/*['maikrəsekənd]微秒*/

public static final TimeUnit MILLISECONDS/*['milisekənd]毫秒*/

public static final TimeUnit MINUTES/*['minit]分钟*/

public static final TimeUnit NANOSECONDS/*['nænəusekənd]纳秒*/

public static final TimeUnit SECONDS/*['sekənd]秒*/

3,成员方法(部分)

public void sleep(long timeout)

throws InterruptedException

在指定的时间段内保持睡眠状态。

public void timedWait(Object obj,long timeout)

throws InterruptedException

让指定的对象,在指定的时间段内保持等待状态。

❻BlockingQueue<E>/*['blɔkiŋ]阻塞[kju:]队列*/接口

public interface BlockingQueue<E>

extends Queue<E>

1,此接口允许队列操作出现阻塞。

2,队列遵循先进先出的规则。

3,若队列为null的增、删、查操作列表

 

抛异常

返回值

阻塞

有时限

插入

add(e)

offer(e)

put(e)

offer(e,time,unit)/*['ɔfə]提供*/

移除

remove(obj)

poll()

take()

poll(time,unit)/*[pəul]修剪*/

检查

element()

peek()

不可用

不可用

4,成员方法(部分)

public boolean contains(Object obj)

判断此队列中是否包含指定元素。

public int drainTo(Collection<? Super E> c)/*[drein]排出*/

将此队列中所有可用的元素,转移到指定的集合中,并返回转移的元素个数。

❼ArrayBlockingQueue<E>/*['blɔkiŋ]阻塞[kju:]队列*/类

public class ArrayBlockingQueue<E>

extends AbstractQueue

implements BlockingQueue<E>,Serializable

1,一个由数组支持的有界阻塞队列。

2,构造方法(部分)

public ArrayBlockingQueue(int capacity)/*[kə'pæsiti]容量*/

构造一个指定大小的数组阻塞队列。

3,成员方法(部分)

public int size()

返回此队列中元素的个数。

4,队列的同步

用队列的方式,实现两个线程间的同步。

先创建两个容量为1的队列A、B,A为空、B为满。一个线程在A中存元素,执行代码,在B中取元素;另一个线程在B中存元素,执行代码,在A中取元素。重复执行以上步骤,可以实现两个线程间的同步操作。

❽CopyOnWriteArrayList<E>类

public class CopyOnWriteArrayList<E>

extends Object

implementsList<E>,RandomAccess,Cloneable,Serializable

1,ArrayList的一个线程安全的变体。允许在迭代的同时,对集合进行操作。

2,构造方法(部分)

public CopyOnWriteArrayList()

创建一个同步的数组列表集合。

❾CopyOnWriteArraySet<E>类

public class CopyOnWriteArraySet<E>

extends AbstractSet<E>

implements Serializable

1,内部的封装了CopyOnWriteArrayList。

2,构造方法(部分)

public CopyOnWriteArraySet()

创建一个同步的数组Set集合。

---------------------------------------- JavaEE+云物联、期待与您交流!---------------------------------------------

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值