JAVA入门学习(十三)——进程、线程的区别,以及线程的休眠、守护线程、多线程的使用、notity()、wait()方法

一、线程与进程

1、进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)

2、进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

3、线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

4、但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

▲5.线程创建的两种方式:
1> 继承Thread类 : 继承它的子类直接new就是线程,详情看代码

Thread类常用方法:
    返回正在运行的线程对象:
		Thread t = Thread.currentThread();
		System.out.println(t);
    返回线程的名称:
		System.out.println(t.getName());
    返回线程的唯一id:
		System.out.println(t.getId());
    判断线程是否可用:
		System.out.println(t.isAlive());
    sleep方法:线程休眠的方法:
		Thread.sleep()
    join方法:等待线程执行完毕
    守护线程
        setDeamon(true);
public class Test07 {
	
	//主线程
	public static void main(String[] args) {
		//启动方法:创建线程的对象
		T t = new T();
		T2 t2 = new T2();
		
		t.start();
		t2.start();
	}
}

class T extends Thread{
	//线程完成功能代码要写在线程体方法中,run()方法
	@Override
	public void run() {
		for(int i=0; i<1000; i++) {
			System.out.println("hello");
			i++;
		}
	}
}

class T2 extends Thread{
	//线程完成功能代码要写在线程体方法中,run()方法
	@Override
	public void run() {
		for(int i=0; i<1000; i++) {
			System.out.println("Thread");
			i++;
		}
	}
}

2>实现Runnable接口:实现该接口的类直接new出来的不是线程,必须作为参数使用Thread类进行创建线程

线程类的构造方法:
Thread()
Thread(String name)
Thread(Runnable r)
Thread(Runnable r,String name)

/**
 * 实现Runnable接口
 * @author 陶沅玱
 *
 */
public class Test01 {

	public static void main(String[] args) {
		Runnable1 runnable1 = new Runnable1();
		Thread t = new Thread(runnable1);
		t.start();
		
		//使用匿名内部类的方式
		Runnable r = new Runnable() {
			
			@Override
			public void run() {
				// TODO Auto-generated method stub
				for(int i=0; i<10; i++) {
					System.out.println(i);
				}
			}
		};
		Thread t2 = new Thread(r);
		t2.start();
		
		//参数匿名内部类
		Thread t3 = new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				for(int i=0; i<100; i++) {
					System.out.println(i);
				}
			}
			
		});
		t3.start();
		
		//使用lambda表达式
		Thread t4 = new Thread(
				()->{
					for(int i=0; i<100; i++) {
						System.out.println(i);
					}
				}
				);
		t4.start();
	}
}

class Runnable1 implements Runnable{

	@Override
	public void run() {
		for(int i=0; i<100; i++) {
			System.out.println(i);
		}
	}	
}

3>共同之处:
代码:都要写在run()方法中
启动:都直接调用start()方法


🔺练习:使用两种方式创建两个线程,一个打印10内的偶数,一个打印10以内的奇数

/**
 * 使用两种方式创建两个线程,一个打印10内的偶数,一个打印10以内的奇数
 * @author 陶沅玱
 *
 */
public class Test01 {

	public static void main(String[] args) {
		Test1 test = new Test1();
		test.start();
		
		//使用lambda表达式  打印10以内的奇数
		Thread t = new Thread(
				()->{
					for(int i=0; i<=10; i++) {
						if(i%2 != 0) {
							System.out.println(i);
						}
					}
				}
				);
		t.start();
	}
}

//打印10内的偶数
class Test1 extends Thread {
	@Override
	public void run() {
		for(int i=0; i<=10; i++) {
			if(i%2 == 0 && i != 0) {
				System.out.println(i);
			}
		}
	}
}


4.线程的休眠 sleep()

/**
 * sleep(毫秒):线程休眠
 *   如果线程休眠的过程被唤醒,会抛出 InterruptedException
 * @author 陶沅玱
 *
 */
public class Test03 {

	public static void main(String[] args) {
		new Thread(
				()->{
					System.out.println(Thread.currentThread().getName());
					for(int i=0; i<10; i++) {
						System.out.println(i);
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}
				, "线程一").start();
	}
}

  1. join()方法:等待线程执行完毕
public class Test05 {

	public static void main(String[] args) {
		Thread r =new Thread() {
			@Override
			public void run() {
				for(int i=0; i<10; i++) {
					System.out.println("Thread-1     " + i);
				}
			}
		};
		Thread r1 =new Thread(
				()->{
					for(int i=0; i<10; i++) {
						System.out.println("Thread-2     " + i);
						if(i == 3) {
							try {
								//当当前r1线程执行到i=3时,要先等到r线程执行完之后,再执行r1线程
								r.join();
							} catch (InterruptedException e) {
								// TODO Auto-generated catch block
								e.printStackTrace();
							}
									
						}
					}
				}
				);
		r1.start();
		r.start();
	}
}

🔺模拟图片的显示与下载

/**
 * 练习:模拟图片显示和图片下载
 * @author 陶沅玱
 *
 */
public class Test06 {

	public static void main(String[] args) {

		Thread t2 = new Thread(
				()->{
					for(int i=1; i<=9; i++) {
						System.out.println("图片下载了" + i*10 +"%");
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}
				);
		
		Thread t1 = new Thread() {
			@Override
			public void run() {
				System.out.println("图片显示成功!");
				try {
					t2.join();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("图片下载成功");
			}
		};
		
		t1.start();
		t2.start();
		
	}
}

6.守护线程:setDeamon(true)

创建出来的线程:用户线程(前台线程)和守护线程(后台线程)
创建的线程默认都是用户线程
设置守护线程,调用setDeamon(true);
用法和创建方式没有区别,
用户线程执行完毕,守护线程无条件停止执行

/**
 * 	测试守护线程
 * @author 陶沅玱
 *
 */
public class Test01 {
	public static void main(String[] args) {
		Thread rose = new Thread() {
			@Override
			public void run() {
				for(int i=0; i<2; i++) {
					System.out.println("一起跳!");
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				System.out.println("挂掉了!");
			}
		};
		Thread jeck = new Thread(
				()->{
					//无限循环
					while(true) {
						System.out.println("你先跳, 我再跳!");
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
				}
				);
		
		rose.start();
		//在启动线程之前设置为守护线程
		jeck.setDaemon(true); 
		jeck.start();
	}
}

结果:
你先跳, 我再跳!
一起跳!
你先跳, 我再跳!
一起跳!
你先跳, 我再跳!
挂掉了!

7.线程的生命周期(从生到死)

新建状态:new Thread() ->即创建了线程的对象
就绪状态:start()方法
阻塞状态; sleep()、wait()、io,解除,重新排队
运行状态:run() ,该方法是自动执行
消亡:执行完run()方法中的语句

8.线程的优先级:

不同优先级:高优先级先执行,低优先级后执行。(高优先级只是具备先执行的权力,不是一定先执行)
同一优先级:先到先服务

线程的优先级规范:系统默认都是中等优先级,可以自定义

/**
* @author 陶沅玱
* 线程优先级
* 线程启动后纳入到线程调度,线程时刻处于被动获取CPU时间片而无法主动获取。
* 我们可以通过调整线程的优先级来最大程度的干涉线程调度分配时间片的几率。
* 理论上优先级越高的线程获取CPU时间片的次数越多。
* 调用线程的方法:setPriority()方法来设置优先级。
* 线程优先级有10个等级,分别用整数1-10表示。其中1位最低优先级,10为最高优先级,5为默认值。
*/
public class Test02 {

	public static void main(String[] args) {
		Thread t1 = new Thread(
				()-> {
			for(int i=0; i<10; i++) {
				System.out.println("t1");
			}
		});
		Thread t2 = new Thread(
				()-> {
			for(int i=0; i<10; i++) {
				System.out.println("t2");
			}
		});
		Thread t3 = new Thread(
				()-> {
			for(int i=0; i<10; i++) {
				System.out.println("t3");
			}
		});	
		//将线程max设置为最大值10
		t3.setPriority(Thread.MAX_PRIORITY);
		//将线程min设置为最小大值1
		t2.setPriority(Thread.MIN_PRIORITY);
		t1.setPriority(Thread.NORM_PRIORITY);
		t1.start();
		t2.start();
		t3.start();
	}
}

9.线程同步:

多个线程操作一个变量时,可能会导致共享变量的不完整(不安全) ,为了保证共享变量的安全,使用同步机制来保证共享变量的安全性

同步机制:当一个线程改变共享变量值的时候,其他的线程不能使用共享变量。当该线程计算完成,返回变量值之后,其他线程才可以使用这个共享变量。

使用synchronized关键字来实现线程同步
同步块synchronized

	synchronized(锁对象){
			共享变量相关的业务代码(运算)
	}

在方法上synchronized,用关键字修饰的方法叫同步方法

🔺线程同步、异步的实验:
<1.卖车票:

public class Test03 {

	static int ticket = 10;

	public static void main(String[] args) {

		Test t1 = new Test();
		Test t2 = new Test();
		
		t1.start();
		t2.start();
	}
}

class Test extends Thread {
	public void run() {
		while (true) {
			if (Test03.ticket < 1) {
				try {
					throw new Exception("票卖完了!");
				} catch (Exception e) {
					// TODO Auto-generated catch block
					System.out.println(e.getMessage());
					System.out.println("aaa");
					break;
				}
			} else {
				synchronized(Test03.class) {
					Test03.ticket--;
					System.out.println(Thread.currentThread().getName()
							+ "	卖出一张票,还剩 " + Test03.ticket);					
				}
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}

<2.商场买衣服

/**
 * 	练习:线程同步:商场买衣服
 * 		商场挑衣服,异步
 * 		试衣间只有一个,试衣服是同步
 * @author 陶沅玱
 *
 */
public class Test01 {
	public static void main(String[] args) {
		Shopping s1 = new Shopping();
		Thread t1 = new Thread(
				()->{
					s1.run();
				}
				);
		Thread t2 = new Thread(
				()->{
					s1.run();
				}
				);
		t1.start();
		t2.start();
	}	
}

class Shopping {
	
	public void run() {
		System.out.println(Thread.currentThread().getName() + "挑衣服!");
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		//试衣服
		synchronized(Test01.class) {
			System.out.println(Thread.currentThread().getName() + "试衣服!");
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

<③.银行存钱

/**
 * 	两个人都往一个账号里存钱
 * @author 陶沅玱
 *	synchronized修饰方法
 */
public class Test02 {

	public static void main(String[] args) {
		Bank bank = new Bank();
		
		Thread t1 = new Thread(
				() -> {
					bank.save(1000);
				}
				);
		Thread t2 = new Thread(
				() -> {
					bank.save(1000);
				}
				);
		t1.start();
		t2.start();
	}
}

class Bank{
	
	private int money = 100;
	
	//synchronized 修饰方法
	public synchronized void save(int money) {
		for(int i=1; i<=3; i++) {
			this.money += money;
			System.out.println(Thread.currentThread().getName() + 
					"余额: "  + this.money);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
}

<4.使用notify()、wait()、notifyAll();方法

public class Test03 {
	public static void main(String[] args) {
		Test3 test = new Test3();		
		new Thread(test).start();
		new Thread(test).start();
		
	}
}

class Test3 implements Runnable{
	int i = 1;	
	@Override
	public void run() {
		
		synchronized(this) {
			while(i<=10) {
				//唤醒wait()的线程
				notify();
				System.out.println(Thread.currentThread().getName() + ": " + i);
				i++;
				try {
					if(i > 10) {
						break;
					}
					//当前线程处于等待状态
					wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
}


🔺注意:sleep()方法和wait()方法的区别:

				  sleep()                               wait()
				线程类的方法							Object类的方法
				不释放锁(例如上述例子中的银行存钱)	释放锁
				规定时间后,自动醒                   必须使用notity()notityAll()方法唤醒

10、线程的死锁

代码示例:
		StringBuffer str1 = new StringBuffer();
		StringBuffer str2 = new StringBuffer();
		synchronized (str1) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			synchronized (str2){
				System.out.println("str1");
				System.out.println("str2");
			}
		}
		synchronized (str2) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			synchronized (str1){
				System.out.println("str1");
				System.out.println("str2");
			}
		}

11、线程池
使用原因:频繁的使用线程池,需要耗费时间的内存
使用线程池的好处:管理线程、使线程重用

import java.text.DateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ThreadPoolDemo {
	//创建一个线程池,如果线程池中的线程数量过大,它可以有效的回收多余的线程,
	//            如果线程数不足,那么它可以创建新的线程。
	public static void test1() throws Exception {
		 
	    ExecutorService threadPool = Executors.newCachedThreadPool();
	 
	    for (int i = 0; i < 5; i++) {
	 
	        final int index = i;
	 
	        Thread.sleep(1000);
	        //完成5个任务
	        threadPool.execute(new Runnable() {
	            @Override
	            public void run() {
	            	System.out.println(Thread.currentThread().getName() + "  " + index);
	            	 try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
	            }
	        });
	    }
	}
	//指定线程池中的线程数,线程数是可以进行控制的
	public static void test2() throws InterruptedException {
		 
	   // ExecutorService threadPool = Executors.newFixedThreadPool(1);
		ExecutorService threadPool = 
				Executors.newFixedThreadPool(3);
	    for (int i = 0; i < 10; i++) {
	 
	        Thread.sleep(1000);
	        final int index = i;
	 
	        threadPool.execute(() -> {
	            try {
	                Thread.sleep(2 * 1000);
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
	            System.out.println(Thread.currentThread().getName() + "  " + index);
	        });
	    }
	    threadPool.shutdown();//不关闭正在执行的线程
	  	//threadPool.shutdownNow();//不管线程是否执行完毕,立即停止执行
	  	System.out.println("关闭线程!");
	}
	//线程池支持定时周期性任务执行
	public static void test3() {
	    ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);
	 
	    threadPool.scheduleAtFixedRate(new Runnable() {
	        @Override
	        public void run() {
	            long start = new Date().getTime();
	            System.out.println("scheduleAtFixedRate 开始执行时间:" +
	                    DateFormat.getTimeInstance().format(new Date()));
	            try {
	                Thread.sleep(5000);
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
	            long end = new Date().getTime();
	            System.out.println("scheduleAtFixedRate 执行花费时间=" + (end - start) / 1000 + "m");
	            System.out.println("scheduleAtFixedRate 执行完成时间:" + DateFormat.getTimeInstance().format(new Date()));
	            System.out.println("======================================");
	        }
	    }, 1, 5, TimeUnit.SECONDS);
	}
	//单线程池,至始至终都由一个线程来执行
	public static void test4() {
	 
	    ExecutorService threadPool = Executors.newSingleThreadExecutor();
	 
	    for (int i = 0; i < 5; i++) {
	        final int index = i;
	        threadPool.execute(() -> {
	            try {
	                Thread.sleep(2 * 1000);
	            } catch (InterruptedException e) {
	                e.printStackTrace();
	            }
	            System.out.println(Thread.currentThread().getName() + "   " + index);
	        });
	    }
	   // threadPool.shutdown();
	}
	
	public static void main(String[] args) throws Exception {
		test2();

	}

}

12.notity()、wait()、notityAll()方法的区别

对于wait()和notify()的理解

对于wait()和notify()的理解,还是要从jdk官方文档中开始,在Object类方法中有:

void notify() 
Wakes up a single thread that is waiting on this object’s monitor. 
译:唤醒在此对象监视器上等待的单个线程

void notifyAll() 
Wakes up all threads that are waiting on this object’s monitor. 
译:唤醒在此对象监视器上等待的所有线程

void wait( ) 
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll( ) method for this object. 
译:导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法

void wait(long timeout) 
Causes the current thread to wait until either another thread invokes the notify( ) method or the notifyAll( ) method for this object, or a specified amount of time has elapsed. 
译:导致当前的线程等待,直到其他线程调用此对象的notify() 方法或 notifyAll() 方法,或者指定的时间过完。


void wait(long timeout, int nanos) 
Causes the current thread to wait until another thread invokes the notify( ) method or the notifyAll( ) method for this object, or some other thread interrupts the current thread, or a certain amount of real time has elapsed. 
译:导致当前的线程等待,直到其他线程调用此对象的notify( ) 方法或 notifyAll( ) 方法,或者其他线程打断了当前线程,或者指定的时间过完。

上面是官方文档的简介,下面我们根据官方文档总结一下:

1.wait( )notify( )notifyAll( )都不属于Thread类,而是属于Object基础类,也就是每个对象都有wait( )notify( )notifyAll( ) 的功能,因为每个对象都有锁,锁是每个对象的基础,当然操作锁的方法也是最基础了。

2.当需要调用以上的方法的时候,一定要对竞争资源进行加锁,如果不加锁的话,则会报 IllegalMonitorStateException 异常

3.当想要调用wait( )进行线程等待时,必须要取得这个锁对象的控制权(对象监视器),一般是放到synchronized(obj)
代码中。

4.while循环里而不是if语句下使用wait,这样,会在线程暂停恢复后都检查wait的条件,并在条件实际上并未改变的情况下
处理唤醒通知

5.调用obj.wait( )释放了obj的锁,否则其他线程也无法获得obj的锁,也就无法在synchronized(obj){ obj.notify() } 
代码段内唤醒A。

6.notify( )方法只会通知等待队列中的第一个相关线程(不会通知优先级比较高的线程)

7.notifyAll( )通知所有等待该竞争资源的线程(也不会按照线程的优先级来执行)

8.假设有三个线程执行了obj.wait( ),那么obj.notifyAll( )则能全部唤醒tread1,thread2,thread3,但是要继续执行
obj.wait()的下一条语句,必须获得obj锁,因此,tread1,thread2,thread3只有一个有机会获得锁继续执行,例如tread1,
其余的需要等待thread1释放obj锁之后才能继续执行。

9.当调用obj.notify/notifyAll后,调用线程依旧持有obj锁,因此,thread1,thread2,thread3虽被唤醒,但是仍
无法获得obj锁。直到调用线程退出synchronized块,释放obj锁后,thread1,thread2,thread3中的一个才有机会获得锁继续执行。

🔺案例(一):

package cn.rjxy.tao15.Test01;

/**
 *   使用线程,完成输出以下功能
 * 12A 34B 56C 78C ....   4950Y 5152Z
 * @author 陶沅玱
 *
 */
public class Test1 {

	public static void main(String[] args) {
		Test1 test = new Test1();
		
		new Thread(new NumThread(test)).start();
		new Thread(new CharThread(test)).start();
	}
}

/**
 *    输出数字的线程
 * @author 陶沅玱
 *
 */
class NumThread implements Runnable{

	private Object obj;
	
	public NumThread(Object obj) {
		this.obj = obj;
	}
	
	@Override
	public void run() {
		synchronized(obj) {
			for(int i=1; i<=52; i++) {
				/**
				 * 方法一
				if(i%2!=0) {
					int a = i+1;
					System.out.print(i + "" + a);
				}
				else {
					try {
						obj.notifyAll();
						obj.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
				 */
				
				/**
				 * 方法二
				 */
				System.out.print(i);
				if(i%2 == 0) {
					obj.notifyAll();
					try {
						obj.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}
	}	
}

/**
 * 	输入字母的线程
 */
class CharThread implements Runnable{

	private Object obj;
	
	public CharThread(Object obj) {
		this.obj = obj;
	}
	
	@Override
	public void run() {
		synchronized(obj) {
			for(char i='A'; i<='Z'; i++) {
				System.out.print(i);
				//唤醒正在等待的线程NumThread
				obj.notifyAll();
				try {
					//
					obj.wait();
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		}
	}
	
}

🔺案例(二):

package cn.rjxy.tao15.Test01;

/**
 *  	使用线程,打印以下:                       	分析:		num=1   ++
	0:1,2,3,4,5,             			 1   16    31    if(num/5%3  == 0)
	1:6,7,8,9,10,            			 6   21    36            %3  == 1
	2:11,12,13,14,15,        			 11  26    41            %3  == 2
	0:16,17,18,19,20,
	1:21,22,23,24,25,
	2:26,27,28,29,30,
	0:31,32,33,34,35,
	1:36,37,38,39,40,
	2:41,42,43,44,45,
	...
         75
 * @author 陶沅玱
 *
 */
public class Test2 {

	public static void main(String[] args) {
		Test2 test = new Test2();
		
		new Thread(new NumThread1(test, 0)).start();
		new Thread(new NumThread1(test, 2)).start();
		new Thread(new NumThread1(test, 1)).start();
	}
}

class NumThread1 implements Runnable{

	private Object obj;
	private int id;
	private static int num = 1;
	public NumThread1(Object obj, int id) {
		super();
		this.obj = obj;
		this.id = id;
	}

	@Override
	public void run() {
		
		synchronized(obj) {
			while(num < 46) {
				if(num/5%3 == id) {
					System.out.print(id + ": ");
					for(int i=0; i<5; i++) {
						System.out.print(num + ", ");
						num++;
					}
					System.out.println();
					obj.notifyAll();
				}
				else {
					try {
						obj.wait();
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
		}
	}
	
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值