多线程

线程概述

        日常生活中,我们会使用电脑的时候会听听音乐,浏览网页,登登QQ或者微博,这些程序则是以进程的形式运行在我们的电脑上。这里我们考虑最简单的情况——你的电脑只有一个CPU。事实上,在某一个时刻,单个CPU只能处理某一个程序,但是由于它的运行速度非常得快,所以才会产生那种可以同时运行的效果。

       CPU在执行程序的时候是毫无规律性的。这也是多线程的一个特性:随机性。无论CPU被哪个线程占用,或者说哪个线程就执行。CPU都不会一直只执行这一个程序,当执行某一个程序后,又会去执行另外一个程序。就这样在各个程序之间进行切换,知道程序执行完成。至于究竟是怎么样执行的,只能由CPU决定。这里所说的程序就是一般意义上的进程,如下图

效果图

下面就让我们一起了解下多线程一下常用的概念吧!

Conception 1 线程

       线程是程序执行的一条路径, 一个进程中可以包含多条线程,但至少得有一个主线程(main),线程之所以存在,是因为在使用一定资源的情况下,提升计算机的运行效率。(JAVA Virtual Machine运行原理)

Conception 2 并行和并发

      并行就是时时刻刻保持一致,而并发则是在某个时间间隔内起点和终点一致即可。

Conception 3 JAVA的多线程

       JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。


线程的生命周期及控制操作

       线程的整个生命周期中无非就是处于新建,就绪,运行,和阻塞等状态,而控制操作则是控制线程从某一状态向另一状态进行转化,因此,我们需要把握清楚各种状态,以及它们之间的转化便可以很快掌握其特性。下面是他们之间相互转化的思维图(本图截于java疯狂讲义 by 李刚):

效果图

重点可以参考JDK API 的Thread类:http://tool.oschina.net/apidocs/apidoc?api=jdk_7u4

创建线程

创建线程有三种方式:

        1.继承并复写Thread的run()方法

             * 定义类继承Thread
             * 重写run方法
             * 把新线程要做的事写在run方法中
             * 创建线程对象
             * 开启新线程, 内部会自动执行run方法

        2.实现Runnable接口

             * 定义类实现Runnable接口
             * 实现run方法
             * 把新线程要做的事写在run方法中
             * 创建自定义的Runnable的子类对象
             * 创建Thread对象, 传入Runnable
             * 调用start()开启新线程, 内部会自动调用Runnable的run()方法  

        3.实现Callable接口

              * 定义类实现Callable接口
          * 实现run方法
          * 把新线程要做的事写在run方法中
          * 创建自定义的Callable的子类对象,获得Future类对象
           * 创建Thread对象, 传入Future类对象
           * 调用start()开启新线程, 内部会自动调用Callable的run()方法  



所谓的线程创建就是将线程所需执行的程序放入run()方法体中,然后通过start()方法启动执行。

<span style="font-size:14px;">package com.heima.thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Demo_Create {

	/**
	 * @param 新建进程
	 * 方式一:继承Thread类
	 * @throws ExecutionException 
	 * @throws InterruptedException 
	 */
	public static void main(String[] args) throws InterruptedException, ExecutionException{
		/**
		 * 继承Thread类复写run()
		 */
		MyThread1 mt1=new MyThread1();
		mt1.start();
		/**
		 * 通过实现Runnable的run()方法定义线程需要执行的任务
		 */
		MyRunnable mr=new MyRunnable();
		new Thread(mr).start();
		
		/**
		 * 通过实现Callable的run()方法定义线程需要执行的任务
		 */
		FutureTask<Integer> futureTask=new FutureTask<Integer>(new MyCallable(50));
		/**
		 * 这里需要注意:
		 * FutureTask在文档中Thread没有调用,但是FutureTask是Runnable的实现类,可以利用这一点
		 */
		new Thread(futureTask).start();	//启动线程,默认会调用call()方法
		System.out.println(futureTask.get());
	}

}

class MyThread1 extends Thread{
    public void run(){
    	for(int i=0;i<100;i++){
    		this.setName("第一个线程");
    		System.out.println(this.getName()+"---------------aaa--------------myThread被调用!");
		}
    }
}
/**
 * 
 * 新建线程2:通过实现Runnable的run()方法定义线程需要执行的任务
 * @author John
 *
 */
class MyRunnable implements Runnable{
	
	@Override
	public void run() {
		for(int i=0;i<100;i++){
			Thread.currentThread().setName("第二个线程");
			System.out.println(Thread.currentThread().getName()+"---------------bbb--------------");
		}
	}
	
}

/**
 * 
 * 新建线程3:通过实现Callable的run()方法定义线程需要执行的任务
 * 注意文档中显示:Callable中的泛型的类型为call()的返回值类型
 * @author John
 *
 */
class MyCallable implements Callable<Integer>{
	
	private int num;
	
	public MyCallable(int num){
		this.num=num;
	}
	
	@Override
	public Integer call() throws Exception {
		int sum=0;
		for(int i=0;i<num;i++){
			sum+=i;
		}
		return sum;
	}
}
</span>


* 继承Thread
    * 好处是:可以直接使用Thread类中的方法,代码简单
    * 弊端是:如果已经有了父类,就不能用这种方法
* 实现Runnable接口或者Callable接口
    * 好处是:即使自己定义的线程类有了父类也没关系,因为有了父类也可以实现接口,而且接口是可以多实现的
    * 弊端是:不能直接使用Thread中的方法需要先获取到线程对象后,才能得到Thread的方法,代码复杂

综上所述,实现Runnable接口很好地解决了单继承问题,然后继承Thread的方式编码相对简单。所以可以根据具体需求进行选择!一般使用Runnable接口实现,

就绪状态

     <span style="font-size:14px;">      MyThread1 mt1=new MyThread1();
		mt1.start();
		//方式一:直接调用线程的start()方法
		MyRunnable mr=new MyRunnable();
		new Thread(mr).start();
                //方式二:将Runnable的实现类作为Thread的构造参数传入,并调用所得线程的start()方法</span>

注:这里需要调用start方法而不是run()方法。如果调用了run()方法,JAVA会把所定义的线程类看成普通的类。


阻塞状态

try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
注:线程从运行状态变为阻塞状态。


守护(后台)线程

主要运用举例:当QQ主页面线程关闭的时候,聊天窗口也关闭。

注:当把某个线程设为守护线程之后,只有当其他非守护线程运行时才可一起运行,当其他非守护线程执行结束后,守护线程也会被迫结束。

<span style="font-size:14px;">package com.heima.thread;

public class Demo_Control {

	/**
	 * @param 新建进程
	 * 方式一:实现Runnable接口
	 */
	public static void main(String[] args){
		MethodTest1 mt1=new MethodTest1();
		/**
		 * 设置后台线程,一旦mt2(其他非守护进程)停止运行,mt1也停止
		 */
		mt1.setDaemon(true);        
		MethodTest2 mt2=new MethodTest2();
		mt1.start();
		new Thread(mt2).start();
	}

}
/**
 * 
 * 新建线程1:通过实现Runnable的run()方法定义线程需要执行的任务
 * @author John
 *
 */
class MethodTest1 extends Thread{

	@Override
	public void run() {
		for(int i=0;i<100;i++){
			//1.让线程由运行状态变为阻塞状态
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
    		Thread.currentThread().setName("test");
    		System.out.println(Thread.currentThread().getName()+"---------------aaaaaaaaaaaaaaaa--------------"+i);
		}
	}
}
/**
 * 
 * 新建线程2:通过实现Runnable的run()方法定义线程需要执行的任务
 * @author John
 *
 */
class MethodTest2 implements Runnable{
	
	@Override
	public void run() {
		for(int i=0;i<5;i++){
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			Thread.currentThread().setName("第二个线程");
			System.out.println(Thread.currentThread().getName()+"---------------bbb--------------");
		}
	}
	
}</span>


加入线程

------join():当前线程暂停运行,等待加入线程运行完毕后方可运行

-----join(int):当前线程暂停运行,等待固定时间后方可运行

package com.heima.thread;

public class Demo_Join {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		final Thread th1=new Thread(){
			public void run(){
				for(int i=0;i<100;i++){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(this.getName()+this.getPriority()+"        "+i);
				}
			}
		};
		
		Thread th2=new Thread(new Runnable() {
			
			@Override
			public void run() {
				for(int i=0;i<100;i++){
					try {
						/**
						 *线程1加入线程2中,那么当线程1阻塞时,线程2需要等到其运行完才可运行
						 */
						th1.join();                                    //th1加入th2
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+Thread.currentThread().getPriority()+"--------"+i);
				}
			}
		});
		th1.start();
		th2.start();
		
	}

}


线程同步

需求:由于CPU切换的随机性,在多线程中多个方法在运行时,我们需要控制CPU执行完线程中的某一个代码块才可以切换时,而不是执行中途切换到其他线程执行方法中。这时我们就需要考虑使用同步机制,从而使CPU这个临界资源得到控制。顾名思义,就是在需要执行某个代码块前给CPU上了把锁,当执行时就获得锁,当执行结束时就释放锁。与此同时可能会带来死锁、持续等待等问题。(注:也就是说所有的同步都需要有把锁,可能为显示可能为隐式的)下面我们来看下具体是怎么实现线程同步的吧:

同步的实现有两种方式:

1.当在synchronize后加上锁对象(任意对象即可),其后跟上的代码块就叫同步代码块。当多个同步代码块中的锁对象相同时,那么他们是线程同步的。

2.当在方法前加上synchronize关键字,那么方法即为同步函数。

那么这里的锁对象是什么呢?

这里隐式表达了锁对象

case1:非静态函数而言是this

case2:静态函数而言是类的字节码对象

注:当我们使用这两种方式构成同步机制的时候,我们需要明确方式二中默认的锁对象是什么,从而才能够在方式一中进行表示

package com.heima.thread;

public class Demo_Synchronized {
	
		/**
		 * 同步方法一:同步代码块,显示表明锁对象
		 */
		public static void printer(){
			System.out.print("黑");
			System.out.print("马");
			System.out.print("程");
			System.out.print("序");
			System.out.print("员");
			System.out.print("\n");
		}
		/**
		 * 同步方法二:静态this,非静态.class
		 */
		public static synchronized void synPrinter(){
			System.out.print("t");
			System.out.print("a");
			System.out.print("n");
			System.out.print("g");
			System.out.print("\n");
		}
			
		public static void main(String[] args){
			new Thread(){
				public void run(){
					for(;;){
						/**
						 * 为了实现同步,而synPrinter()为静态方法,所以锁对象即为.class
						 */
						synchronized (Demo_Synchronized.class) {
							printer();
						}
					}
					}
			}.start();
			new Thread(){
				public void run(){
				for(;;){
					synPrinter();
					}
				}
			}.start();
		}
}







知晓某个类是否线程安全:在IDE中Ctrl+Shift+T查看类的源代码,再按Ctrl+O查看增加方法前面有没有synchronized关键字修饰(个人觉得可以按Ctrl+F看有没有synchronized关键字修饰方法)。例如:StringBuffer是线程安全的,StringBuilder是线程不安全的。

线程通信

需求:由于CPU切换的随机性,当我们需要使用多线程很有秩序的完成某项任务时就要考虑使用线程通信。首先我们先得弄清楚两个线程之间通信是如何完成,接下来再完成多个线程(三个)之间的通信。

这里要使用到Object类中的wait()方法和notify()以及notifyAll()。

需要注意:notify()是随机唤醒单个线程,notifyAll()是唤醒所有线程。

两个线程间通信的情况:

package com.heima.threadcomnunication;

public class DemoSynTwo {

	public static void main(String[] args) {
		final Printer p=new Printer();
		new Thread(){
			public void run(){
				try {
					while(true){
						p.printer1();
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}.start();
		
		
		new Thread(){
			public void run(){
				try {
					while(true){
						p.printer2();
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}.start();
	}

}
/**
 * 注意:1.同步代码块中的锁对象即为调用wait()方法和notifyAll()方法,之所以将这两个方法定义在Object类中是因为Object是所以类的父类。
 * 2.wait()和sleep()的区别:
 *  1).wait()可传入参数,且参数代表需要运行多长时间后然后进入阻塞状态,sleep()可传入参数,传入参数为需要等待多长时间。即wait是通过参数
 * 知道多长时间后开始等待,而sleep则是通过参数得知等待多长时间
 *  2).wait陷入阻塞状态之后,释放同步锁,即睡着了锁就被扔了
 * sleep陷入阻塞状态之后,不释放同步锁,即睡着了还抱着锁
 *  3).wait会一直等待知道被notify唤醒,而sleep是等待一定时间后自动被唤醒
 *  3.notify()是随机通知单个线程,notifyAll()是通知所有线程
 * @author John
 */

class Printer{
	private int flag=1;
	
	public void printer1() throws InterruptedException{
		
		synchronized(this){
			if(flag!=1){
				this.wait();
			}
			System.out.print("黑");
			System.out.print("马");
			System.out.print("程");
			System.out.print("序");
			System.out.print("员");
			System.out.print("\n");
			flag=2;
			this.notify();
		}
	}
	public void printer2() throws InterruptedException{
		synchronized(this){
			if(flag!=2){
				this.wait();
			}
			System.out.print("传");
			System.out.print("智");
			System.out.print("播");
			System.out.print("课");
			System.out.print("\n");
			flag=1;
			this.notify();
		}
	}
}

两个以上线程间通信的情况(JDK1.5以前实现):

package com.heima.threadcomnunication;

public class DemoSynThree {

	public static void main(String[] args) {
		final Printer2 p=new Printer2();
		new Thread(){
			public void run(){
				try {
					while(true){
						p.printer1();
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}.start();
		
		new Thread(){
			public void run(){
				try {
					while(true){
						p.printer2();
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}.start();
		
		new Thread(){
			public void run(){
				try {
					while(true){
						p.printer3();
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}.start();
	}

}
/**
 * 注意:1.同步代码块中的锁对象即为调用wait()方法和notifyAll()方法,之所以将这两个方法定义在Object类中是因为Object是所以类的父类。
 * 2.wait()和sleep()的区别:
 *  1).wait()可传入参数,且参数代表需要运行多长时间后然后进入阻塞状态,sleep()可传入参数,传入参数为需要等待多长时间。即wait是通过参数
 * 知道多长时间后开始等待,而sleep则是通过参数得知等待多长时间
 *  2).wait陷入阻塞状态之后,释放同步锁,即睡着了锁就被扔了
 * sleep陷入阻塞状态之后,不释放同步锁,即睡着了还抱着锁
 *  3).wait会一直等待知道被notify唤醒,而sleep是等待一定时间后自动被唤醒
 *  3.notify()是随机通知单个线程,notifyAll()是通知所有线程
 * @author John
 */

class Printer2{
	private int flag=1;
	
	public void printer1() throws InterruptedException{
		
		synchronized(this){
			/*if(flag!=1){
				this.wait();
			}*/
			while(flag!=1){
				this.wait();
			}
			System.out.print("黑");
			System.out.print("马");
			System.out.print("程");
			System.out.print("序");
			System.out.print("员");
			System.out.print("\n");
			flag=2;
//			this.notify();
			this.notifyAll();
		}
	}
	public void printer2() throws InterruptedException{
		synchronized(this){
			/*if(flag!=2){
				this.wait();
			}*/
			while(flag!=2){
				this.wait();
			}
			System.out.print("传");
			System.out.print("智");
			System.out.print("播");
			System.out.print("课");
			System.out.print("\n");
			flag=3;
//			this.notify();
			this.notifyAll();
		}
	}
	public void printer3() throws InterruptedException{
		synchronized(this){
			/*if(flag!=2){
				this.wait();
			}*/	
			while(flag!=3){
				this.wait();
			}
			System.out.print("t");
			System.out.print("a");
			System.out.print("n");
			System.out.print("g");
			System.out.print("\n");
			flag=1;
//			this.notify();
			this.notifyAll();
		}
	}
}

注意:此时使用notify()可能会造成死锁


两个以上线程间通信的情况(JDK1.5以后实现):

程序更新:

package com.heima.threadcomnunication;

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

public class DemoReentrantLock {

	public static void main(String[] args) {
		final Printer3 p=new Printer3();
		new Thread(
				new Runnable(){
					public void run(){
					try {
						while(true)
						p.printer1();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}	
					}
				}).start();
		new Thread(
				new Runnable(){
					public void run(){
						try {
							while(true)
							p.printer2();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}	
					}
				}).start();
		new Thread(
				new Runnable(){
					public void run(){
						try {
							while(true)
							p.printer3();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}	
					}
				}).start();
	}
	
}
 /**
  * 注意:1.同步代码块中的锁对象即为调用wait()方法和notifyAll()方法,之所以将这两个方法定义在Object类中是因为Object是所以类的父类。
  * 2.wait()和sleep()的区别:
  *  1).wait()可传入参数,且参数代表需要运行多长时间后然后进入阻塞状态,sleep()可传入参数,传入参数为需要等待多长时间。即wait是通过参数
  * 知道多长时间后开始等待,而sleep则是通过参数得知等待多长时间
  *  2).wait陷入阻塞状态之后,释放同步锁,即睡着了锁就被扔了
  * sleep陷入阻塞状态之后,不释放同步锁,即睡着了还抱着锁
  *  3).wait会一直等待知道被notify唤醒,而sleep是等待一定时间后自动被唤醒
  *  3.notify()是随机通知单个线程,notifyAll()是通知所有线程
  * @author John
  */
class Printer3{
	private int flag=1;
	/**
	 * 加入同步机制,JDK1.5新特性
	 */
	private ReentrantLock lock=new ReentrantLock();//用来代替synchronized
	/**
	 * 给每个线程添加监听器,控制线程的唤醒和等待
	 * @throws InterruptedException
	 */
	private Condition con1=lock.newCondition();
	private Condition con2=lock.newCondition();
	private Condition con3=lock.newCondition();

	public void printer1() throws InterruptedException{
			lock.lock();
			if(flag!=1){
				con1.await();
			}
			System.out.print("黑");
			System.out.print("马");
			System.out.print("程");
			System.out.print("序");
			System.out.print("员");
			System.out.print("\n");
			flag=2;
			con2.signal();
			lock.unlock();
	}
	public void printer2() throws InterruptedException{
			lock.lock();
			if(flag!=2){
				con2.await();
			}
			System.out.print("传");
			System.out.print("智");
			System.out.print("播");
			System.out.print("课");
			System.out.print("\n");
			flag=3;
			con3.signal();
			lock.unlock();
	}
	public void printer3() throws InterruptedException{
			lock.lock();
			if(flag!=3){
				con3.await();
			}
			System.out.print("t");
			System.out.print("a");
			System.out.print("n");
			System.out.print("g");
			System.out.print("\n");
			flag=1;
			con1.signal();
			lock.unlock();
	}
}


线程组

 线程组的产生是为了便于管理线程,详细可参见ThreadGroup类

线程池

线程池的产生是为了减少线程创建所需的资源消耗,详情可参见ExecutorServciel类


补充知识点

单例设计模式

保证内存中仅有一个类对象,从而控制该类的访问权。

实现方式:

懒汉式

每次创建对象前先判断下是否为空,若为空则创建对象,否则不创建。

package com.heima.singleton;

public class Demo_Lazy {
		
	public  static void main(String[] args){
		
		Singleton singleton1=Singleton.getInstance();
		Singleton singleton2=Singleton.getInstance();
		System.out.println(singleton1);
		System.out.println(singleton2);
		/**
		 * com.heima.singleton.Singleton@1fb8ee3
		 * com.heima.singleton.Singleton@61de33
		 * ?????首次创建和第二次创建的不是一个对象
		 */
		
	}
}




/**
 *懒汉式 
 */
class Singleton{
	
	private static Singleton singleton;
	
	private Singleton(){
	}
	
	public static Singleton getInstance(){
		if(singleton==null){
			/**
			 * 注意当多线程中可能会产生创建多个对象的问题
			 */
			return new Singleton();
		}
		return singleton;
	}
	
	public void say(){
		System.out.println("hello world!!!");
	}
}


这里产生一个问题:第一次和第二次创建的对象hashCode不相同

饿汉式

每次访问时内部调用构造器进行创建

package com.heima.singleton;

public class Demo_Hungry{
		
	public  static void main(String[] args){
		
		Hungry singleton1=Hungry.getInstance();
		Hungry singleton2=Hungry.getInstance();
		System.out.println(singleton1);
		System.out.println(singleton2);
		/**
		 * com.heima.singleton.Hungry@1fb8ee3
		 * com.heima.singleton.Hungry@1fb8ee3
		 */
	}
}




/**
 *饿汉式 
 */
class Hungry{
	
	private final static Hungry hungry=new Hungry();
	
	/**
	 * 私有化构造器,控制外界实例化的权限
	 */
	private Hungry(){
	}
	
	public static Hungry getInstance(){
		return hungry;
	}
	
	public void say(){
		System.out.println("hello world!!!");
	}
}


具体使用:Runtime使用的就是单例设计模式

工厂设计模式

工厂设计模式就是已知了对象的构成元素,那么将对象的生成等工作直接交给了工厂。

这里有三个类:

Animal

package com.heima.factory;

public class Animal {

	public void eat(){
		System.out.println("动物吃东西");
	}	 
	
}


Dog

package com.heima.factory;

public class Dog extends Animal {
		 
	public void eat(){
		System.out.println("狗吃肉");

	}
}


Cat

package com.heima.factory;

public class Cat extends Animal {
		 
	public void eat(){
		System.out.println("猫吃鱼");
	}
}



简单工厂模式

图片


如图,利用多态性,我们可以将同类事物的生成全部交给同一个工厂来完成。

左图工厂的实现方式:

package com.heima.factory;

public class AnimalFactory2 {

	
	public static Animal getAnimal(String name){
		
		if("DOG".equals(name.toUpperCase())){
			return new Dog();
		}else if("CAT".equals(name.toUpperCase())) {
			return new Cat();
		}else{
			return null;
		}
		
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
			Dog dog=(Dog) AnimalFactory2.getAnimal("dog");
			dog.eat();
			Cat cat=(Cat) AnimalFactory2.getAnimal("cT");
			cat.eat();
	}

}

注:可能会存在空指针异常


右图工厂的实现方式:

package com.heima.factory;

public class AnimalFactory {

	public static Animal getDog(){
		return new Dog();
	}
	
	public static Animal getCat(){
		return new Cat();
	}
	
	
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
			Dog dog=(Dog) AnimalFactory.getDog();
			dog.eat();
			
			Cat cat=(Cat) AnimalFactory.getCat();
			cat.eat();
			
	}

}


一般工厂模式

对于每一类对象都有自己的生成工厂。

图片

Factory

package com.heima.factory;

public interface Factory {
		 
	  public Animal getObject();
	    
}

CatFactory


package com.heima.factory;

public class CatFactory implements Factory {

	@Override
	public Animal getObject() {
		return new Cat();
	}

}

DogFactory

package com.heima.factory;

public class DogFactroy implements Factory {

	@Override
	public Animal getObject() {
		return new Dog();
	}

	
}




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值