java1.7线程暂停_线程的停止与暂停

1.停止线程

停止线程不像停止一个循环break一样干脆。

停止一个线程意味着在线程处理完任务之前停掉正在做的操作,也就是放弃当前的操作。虽然看起来简单,但是必须做好正确的防范措施,以便达到预期的效果。停止一个线程可以用Thread.stop(),但最好不要用它。虽然它确实可以停止一个正在运行的线程,但是这个方法是不安全的,而且已经作废的方法。

大多数停止一个线程用Thread.interrupt()方法,尽管方法的名称是"中止,停止"的意思,但这个方法不一定会停止一个正在运行的线程,还需要加入一个判断才可以完成线程的停止。

在Java中有3种方法可以停止正在运行的线程:

(1)使用退出标志使线程正常终止,也就是当run方法完成后线程终止。

(2)使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend、resume一样,都是过期作废的方法。

(3)使用interrupt方法中断线程。

1.停止不了的线程

调用thread.interrupt()方法,但是此方法并不会马上停止线程,只是在当前线程打了一个停止的标记,并不是真正的停止线程。

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;public class Demo1 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo1.class);

@Overridepublic voidrun() {for (int i = 0; i < 50;) {

log.debug("i->{}", i++);

}

}public static voidmain(String[] args) {try{

Demo1 demo1= newDemo1();

demo1.start();

Thread.sleep(200);//发出中断线程信号

demo1.interrupt();

}catch(InterruptedException e) {

log.error("main catch ", e);

}

}

}

结果:(仍然会执行完线程的run方法,也就是interrupt()方法并没有中断线程)

1801a9e546117534bfbf91fce7af42fc.png

2.判断线程是否终止

在Java的SDK种,提供了两个方法判断线程是否终止:

(1)this.interrupted(),测试当前线程是否已经中止(静态方法------测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能)

public static booleaninterrupted() {return currentThread().isInterrupted(true);

}

(2)this.isInterrupted(),测试线程是否已经中断。(实例方法-----测试线程对象Thread对象是否已经是中断状态,但不清除状态标志)

public booleanisInterrupted() {return isInterrupted(false);

}

可以看出上面两个方法都调用了isInterrupted(boolean)方法,查看源码:(参数是是否重置状态)

/*** Tests if some Thread has been interrupted. The interrupted state

* is reset or not based on the value of ClearInterrupted that is

* passed.*/

private native boolean isInterrupted(boolean ClearInterrupted);

this.interrupted()静态方法的研究--执行任何线程的此方法都相当于执行Thread.currentThread.interrupted(),会返回线程的中断状态并且会清除中断状态

测试当前线程是否已经中断,当前线程是指运行this.interrupted()方法的线程。

测试代码:

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;public class Demo2 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo2.class);

@Overridepublic voidrun() {for (int i = 0; i < 5000000;) {

}

}public static voidmain(String[] args) {try{

Demo2 demo2= newDemo2();

demo2.start();

Thread.sleep(1000);//发出中断线程信号

demo2.interrupt();

log.debug("是否停止1?{}", demo2.interrupted());

log.debug("是否停止2?{}", demo2.interrupted());

}catch(InterruptedException e) {

log.error("main catch ", e);

}

log.debug("end");

}

}

结果:

2018-12-07 [cn.qlq.thread.three.Demo2]-[DEBUG] 是否停止1?false

2018-12-07 [cn.qlq.thread.three.Demo2]-[DEBUG] 是否停止2?false

2018-12-07 [cn.qlq.thread.three.Demo2]-[DEBUG] end

解释:虽然调用的是demo2.interrupted()方法,但是我们从源码也可以看出测的是当前线程,当前线程就是执行当前代码的线程,也就是main线程,所以打印了两个false。

如何测试main线程的中断效果:

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;public class Demo3 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo3.class);public static voidmain(String[] args) {

Thread.currentThread().interrupt();//发出中断线程信号

log.debug("是否停止1?{}", Thread.interrupted());

log.debug("是否停止2?{}", Thread.interrupted());

}

}

结果:

2018-12-07 [cn.qlq.thread.three.Demo3]-[DEBUG] 是否停止1?true

2018-12-07 [cn.qlq.thread.three.Demo3]-[DEBUG] 是否停止2?false

第二个参数是返回值是false的原因?官网对此方法的解释:

测试当前线程是否已经中断(当前线程是指执行当前代码的线程)。线程的中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回false(在第一次调用已清除了其中状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。

解释已经很清楚了,interrupted()方法具有清除状态的功能,所以第二次调用返回的是false。

isInterrupyed()方法的研究-----检测线程对象是否中断的状态,并且不会清除状态。

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;public class Demo5 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo5.class);

@Overridepublic voidrun() {for (int i = 0; i < 5000000;) {

}

}public static voidmain(String[] args) {try{

Demo5 demo2= newDemo5();

demo2.start();

Thread.sleep(1000);//发出中断线程信号

log.debug("是否停止0?{}", demo2.isInterrupted());

demo2.interrupt();

log.debug("是否停止1?{}", demo2.isInterrupted());

log.debug("是否停止2?{}", demo2.isInterrupted());

}catch(InterruptedException e) {

log.error("main catch ", e);

}

log.debug("end");

}

}

结果:

2018-12-07 [cn.qlq.thread.three.Demo5]-[DEBUG] 是否停止0?false

2018-12-07 [cn.qlq.thread.three.Demo5]-[DEBUG] 是否停止1?true

2018-12-07 [cn.qlq.thread.three.Demo5]-[DEBUG] 是否停止2?true

2018-12-07 [cn.qlq.thread.three.Demo5]-[DEBUG] end

3.能停止的线程 --- 异常法

在for循环种检测是否收到中断信号,收到中断信号就break。

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/*** for循环检测是否是停止状态,如果是停止状态就不执行后面代码

*

*@authorAdministrator

**/

public class Demo4 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo4.class);

@Overridepublic voidrun() {super.run();for (int i = 0; i < 50; i++) {if(Thread.interrupted()) {

log.debug("线程已经终止,我要退出");break;

}

log.debug("i={}", i);

}

}public static voidmain(String[] args) {try{

Demo4 demo4= newDemo4();

demo4.start();

Thread.sleep(3);//发出中断信号

demo4.interrupt();

}catch(InterruptedException e) {

log.error("InterruptedException ", e);

}

log.debug("end");

}

}

结果:

2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] i=0

2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] i=1

2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] end

2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] 线程已经终止,我要退出

上面有个问题,如果for循环后面有语句还是会执行后面的语句,解决办法如下:(思路就是合理的利用异常机制)

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/*** for循环检测是否是停止状态,如果是停止状态就不执行后面代码

*

*@authorAdministrator

**/

public class Demo6 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo4.class);

@Overridepublic voidrun() {super.run();try{for (int i = 0; i < 50; i++) {if(Thread.interrupted()) {

log.debug("线程已经终止,我要退出");//抛出异常

throw newInterruptedException();

}

log.debug("i={}", i);

}

log.debug("for 后面的语句");

}catch(InterruptedException e) {

log.error("进入catch方法---", e);

}

}public static voidmain(String[] args) {try{

Demo6 demo6= newDemo6();

demo6.start();

Thread.sleep(3);//发出中断信号

demo6.interrupt();

}catch(InterruptedException e) {

log.error("InterruptedException ", e);

}

log.debug("end");

}

}

结果:

2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] i=0

2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] end

2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] 线程已经终止,我要退出

2018-12-07 [cn.qlq.thread.three.Demo4]-[ERROR] 进入catch方法---

java.lang.InterruptedException

at cn.qlq.thread.three.Demo6.run(Demo6.java:24)

4.在沉睡种停止

如果线程在sleep中中断,会是什么效果?

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/*** 沉睡中中断会进捕捉到中断异常

*

*@authorAdministrator

**/

public class Demo7 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo4.class);

@Overridepublic voidrun() {super.run();try{

Thread.sleep(50 * 1000);

}catch(InterruptedException e) {

log.error("进入catch方法");

log.info("demo7.isInterrupted 2-> {}", this.isInterrupted());

}

}public static voidmain(String[] args) {

Demo7 demo7= newDemo7();

demo7.start();//发出中断信号

demo7.interrupt();

log.info("demo7.isInterrupted 1-> {}", demo7.isInterrupted());

log.debug("end");

}

}

结果:

2018-12-07 [cn.qlq.thread.three.Demo4]-[ERROR] 进入catch方法

2018-12-07 [cn.qlq.thread.three.Demo4]-[INFO] demo7.isInterrupted 1-> true

2018-12-07 [cn.qlq.thread.three.Demo4]-[INFO] demo7.isInterrupted 2-> false

2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] end

可以看出线程在sleep状态下中断某一线程会进入catch语句,并且清除停止状态值,使之变为false。(这里也就解释了为什么sleep(long)抛出一个检查型异常(InterruptedException))。

上面代码是先中断后sleep,其实先interrupt后sleep也是一样的错误,如下:

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/*** 先中断后睡眠也会进入异常

*

*@authorAdministrator

**/

public class Demo8 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo4.class);public static voidmain(String[] args) {//发出中断信号

Thread.currentThread().interrupt();try{

Thread.sleep(50000);

}catch(InterruptedException e) {

log.debug("interruptedException", e);

}

}

}

结果:

2018-12-07 [cn.qlq.thread.three.Demo4]-[DEBUG] interruptedException

java.lang.InterruptedException: sleep interrupted

at java.lang.Thread.sleep(Native Method)

at cn.qlq.thread.three.Demo8.main(Demo8.java:20)

5.能停止的线程------stop 暴力停止(释放锁,容易造成数据不一致)

源码如下:

@Deprecatedpublic final voidstop() {

stop(newThreadDeath());

}

@Deprecatedpublic final synchronized voidstop(Throwable obj) {if (obj == null)throw newNullPointerException();

SecurityManager security=System.getSecurityManager();if (security != null) {

checkAccess();if ((this != Thread.currentThread()) ||(!(obj instanceofThreadDeath))) {

security.checkPermission(SecurityConstants.STOP_THREAD_PERMISSION);

}

}//A zero status value corresponds to "NEW", it can't change to//not-NEW because we hold the lock.

if (threadStatus != 0) {

resume();//Wake up thread if it was suspended; no-op otherwise

}//The VM can handle all thread states

stop0(obj);

}private native void stop0(Object o);

使用stop停止线程是非常暴力的。

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/*** stop暴力停止

*

*@authorAdministrator

**/

public class Demo9 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo9.class);

@Overridepublic voidrun() {int i = 0;while (true) {

log.debug("{}", i++);

}

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

Demo9 demo9= newDemo9();

demo9.start();

Thread.sleep(100);//暴力停止

demo9.stop();

log.debug("end");

}

}

结果:

65f09e37369deaf930eb2beb3f70e412.png

可以看到直接杀死线程,解除由该线程获得的所有对象锁头,而且可能使对象处于不连贯状态,如果其他线程访问对象,而导致的错误很难检查。所以被废弃。

调用stop()方法时会抛出java.lang.ThreadDeath,但是在通常情况下,此异常不需要显示的捕捉。(在JDK7中已经没有抛出异常了,查看上面源码也可以知道)

方法stop()已经作废,因为如果强制性让一个线程停止则有可能使一些清理性的工作得不到完成。另外的情况就是对锁定的对象进行了"解锁",导致数据得不到同步的处理,出现数据不一致的情况。

使用stop释放锁的不良后果:

使用stop释放锁会造成数据不一致的情况。如果出现这样的情况,程序处理的数据就可能遭到破坏,最终导致程序执行的错误。

例如:(此处需要理解引用类型)

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/*** stop暴力停止释放锁造成的数据不一致

*

*@authorAdministrator

**/

public class Demo10 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo10.class);privateSyncObj syncObj;publicDemo10(SyncObj syncObj) {this.syncObj =syncObj;

}

@Overridepublic voidrun() {

syncObj.setValue("b", "bbb");

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

SyncObj syncObj= newSyncObj();

Demo10 demo9= newDemo10(syncObj);

demo9.start();

Thread.sleep(5 * 1000);//暴力停止

demo9.stop();

log.debug("syncObj - > {}", syncObj);

}

}classSyncObj {private String username = "a";private String password = "aaa";publicString getUsername() {returnusername;

}public voidsetUsername(String username) {this.username =username;

}publicString getPassword() {returnpassword;

}public voidsetPassword(String password) {this.password =password;

}public synchronized voidsetValue(String username, String password) {try{this.username =username;//休眠10秒中

Thread.sleep(10 * 1000);this.password =password;

}catch(InterruptedException e) {

e.printStackTrace();

}

}

@OverridepublicString toString() {return "SyncObj [username=" + username + ", password=" + password + "]";

}

}

结果:(由于停止线程造成的数据不一致,只修改了username,没有修改密码)

2018-12-07 [cn.qlq.thread.three.Demo10]-[DEBUG] syncObj - > SyncObj [username=b, password=aaa]

6.使用return停止线程

将interrupt与return结合也可以很好的实现停止线程的效果。

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/*** return + interrupt结束线程

*

*@authorAdministrator

**/

public class Demo11 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo11.class);

@Overridepublic voidrun() {while (true) {if (this.isInterrupted()) {

log.debug("run收到终止信号,终止了!");return;

}

log.debug("sss");

}

}public static void main(String[] args) throwsInterruptedException {//暴力停止

Demo11 demo11 = newDemo11();

demo11.start();

Thread.sleep(100);

demo11.interrupt();

}

}

结果:

04a50ffd5244d79aef7cbf4356ee8621.png

建议使用抛异常的方法来实现线程的停止,因为在catch中还可以向上抛,使线程传播的事件得以传播。

2.暂停线程

暂停线程意味着可以恢复运行。在Java多线程编程中,可以使用suspend()方法暂停线程,使用resume()恢复线程。这两个方法都是过期作废的方法。

源码如下:

@Deprecatedpublic final voidsuspend() {

checkAccess();

suspend0();

}

@Deprecatedpublic final voidresume() {

checkAccess();

resume0();

}private native void setPriority0(intnewPriority);private native voidstop0(Object o);private native voidsuspend0();private native voidresume0();private native voidinterrupt0();private native void setNativeName(String name);

1.suspend()和resume()的使用

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;

public class Demo12 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo12.class);private longi;

@Overridepublic voidrun() {while (true) {

i++;

}

}public static void main(String[] args) throwsInterruptedException {//暴力停止

Demo12 demo12 = newDemo12();

demo12.start();//A段

Thread.sleep(500);

demo12.suspend();

log.debug("time1->{},i->{}", System.currentTimeMillis(), demo12.getI());

Thread.sleep(500);

log.debug("time2->{},i->{}", System.currentTimeMillis(), demo12.getI());//B段

demo12.resume();

Thread.sleep(500);//C段

Thread.sleep(500);

demo12.suspend();

log.debug("time3->{},i->{}", System.currentTimeMillis(), demo12.getI());

Thread.sleep(500);

log.debug("time4->{},i->{}", System.currentTimeMillis(), demo12.getI());

}public longgetI() {returni;

}public void setI(longi) {this.i =i;

}

}

结果:

2018-12-07 [cn.qlq.thread.three.Demo12]-[DEBUG] time1->1544187679161,i->89828826

2018-12-07 [cn.qlq.thread.three.Demo12]-[DEBUG] time2->1544187679668,i->89828826

2018-12-07 [cn.qlq.thread.three.Demo12]-[DEBUG] time3->1544187680670,i->298679684

2018-12-07 [cn.qlq.thread.three.Demo12]-[DEBUG] time4->1544187681170,i->298679684

从控制台打印的时间以及结果看,线程确实被暂停了,而且也可以恢复。

2.suspend()和resume()的缺点---独占(也就是suspend不会释放锁)

在使用suspend()和resume()的时候极易造成公共的同步对象的独占,使其他线程无法访问公共同步对象。

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;public class Demo13 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo13.class);privateSyncObj2 syncObj2;publicDemo13(String name, SyncObj2 syncObj2) {this.setName(name);this.syncObj2 =syncObj2;

}

@Overridepublic voidrun() {

syncObj2.testSync();

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

SyncObj2 syncObj2= newSyncObj2();

Demo13 demo131= new Demo13("a", syncObj2);

demo131.start();//休眠是为了使上面线程占用锁

Thread.sleep(1000);

Demo13 demo132= new Demo13("b", syncObj2);

demo132.start();//打印状态

System.out.println("demo131.getState()->" +demo131.getState());

System.out.println("demo132.getState()->" +demo132.getState());

}

}classSyncObj2 {public synchronized voidtestSync() {

System.out.println("进入testSync同步方法");if ("a".equals(Thread.currentThread().getName())) {

System.out.println("此线程将挂起,永远不会释放锁,其他线程无法调用此方法");

Thread.currentThread().suspend();

}

System.out.println("退出testSync同步方法");

}

}

结果:(suspend之后线程处于可运行状态,等待锁的处于阻塞状态)

进入testSync同步方法

此线程将挂起,永远不会释放锁,其他线程无法调用此方法

demo131.getState()->RUNNABLE

demo132.getState()->BLOCKED

jvisualvm查看线程状态:

904ce6ac8f1f3eef73d00c6aeb83aedb.png

另外一个需要注意的常见写法:(关于System.out.print()的同步)

packagecn.qlq.thread.three;public class Demo14 extendsThread {inti;

@Overridepublic voidrun() {while (true) {

System.out.println(i++);

}

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

Demo14 demo14= newDemo14();

demo14.start();

Thread.sleep(100);

demo14.suspend();

System.out.println("main end");

}

}

结果:(到最后都不会打印main end)

10a0f44c817e52d40ac514791acd8ec6.png

原因:println方法是一个同步方法,所以线程suspend之后没有释放锁,源码如下:

public voidprintln(String x) {synchronized (this) {

print(x);

newLine();

}

}

3.suspend()和resume()的缺点---不同步

在使用suspend()和resume()的时候也容易出现因为线程的暂停而导致数据不同步的情况。

packagecn.qlq.thread.three;importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;/*** suspend和resume导致数据不同步

*

*@authorQiaoLiQiang

* @time 2018年12月7日下午9:32:50*/

public class Demo15 extendsThread {private static final Logger log = LoggerFactory.getLogger(Demo15.class);privateSyncObj3 syncObj3;

@Overridepublic voidrun() {

syncObj3.setVal("b", "bbb");//启动线程设置值

}publicDemo15(String name, SyncObj3 syncObj3) {this.setName(name);this.syncObj3 =syncObj3;

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

SyncObj3 syncObj3= newSyncObj3();

Demo15 demo15= new Demo15("b", syncObj3);

demo15.start();

log.debug("main start");

Thread.sleep(3 * 1000);

demo15.suspend();//暂停线程

Thread.sleep(5 * 1000);

log.debug("syncObj31->{}", syncObj3);

demo15.resume();//恢复线程

Thread.sleep(1 * 1000);

log.debug("syncObj32->{}", syncObj3);

Thread.sleep(2000);

log.debug("syncObj33->{}", syncObj3);

}

}classSyncObj3 {private String username = "a";private String password = "aaa";private static final Logger log = LoggerFactory.getLogger(SyncObj3.class);public voidsetVal(String username, String password) {this.username =username;try{

log.debug("{}线程将要休眠5秒钟", Thread.currentThread().getName());

Thread.sleep(5 * 1000);

log.debug("{}线程睡醒了", Thread.currentThread().getName());

}catch(InterruptedException e) {

e.printStackTrace();

}

log.debug("{}线程设置密码的值,password->{}", Thread.currentThread().getName(), password);this.password =password;

}publicString getUsername() {returnusername;

}public voidsetUsername(String username) {this.username =username;

}publicString getPassword() {returnpassword;

}public voidsetPassword(String password) {this.password =password;

}

@OverridepublicString toString() {return "SyncObj3 [username=" + username + ", password=" + password + "]";

}

}

结果:

21:58:51 [cn.qlq.thread.three.Demo15]-[DEBUG] main start

21:58:51 [cn.qlq.thread.three.SyncObj3]-[DEBUG] b线程将要休眠5秒钟

21:58:59 [cn.qlq.thread.three.Demo15]-[DEBUG] syncObj31->SyncObj3 [username=b, password=aaa]

21:58:59 [cn.qlq.thread.three.SyncObj3]-[DEBUG] b线程睡醒了

21:58:59 [cn.qlq.thread.three.SyncObj3]-[DEBUG] b线程设置密码的值,password->bbb

21:59:00 [cn.qlq.thread.three.Demo15]-[DEBUG] syncObj32->SyncObj3 [username=b, password=bbb]

21:59:02 [cn.qlq.thread.three.Demo15]-[DEBUG] syncObj33->SyncObj3 [username=b, password=bbb]

解释:main线程和b线程线程开始之后,主线程睡了3秒钟之后暂停了b线程,b线程此时也睡了3秒钟(还剩余睡眠2秒钟),暂停5秒钟之后恢复b线程,恢复之后就马上执行睡眠之后的代码(也就是暂停前的代码),所以没有继续睡眠之前剩余的两秒钟。总结起来:线程恢复之后会继续执行暂停时的代码,而且暂停过程中睡眠时间也在走(暂停不会导致睡眠时间的延迟)。

总结:

suspend()方法可以暂停线程,而且不会释放同步锁,而且暂停不会导致睡眠时间的延长;

resume()可以使线程恢复状态,而且会继续执行暂停前的剩余代码。

上面两个方法都是过期作废的方法。

文中用到的相关知识点:引用传递、一个对象一把锁、synchronized方法实则是对当前对象加锁、synchronized同步代码块(关于同步会在下篇继续学习)。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值