1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Java笔记 - 黑马程序员_07(多线程 线程同步 线程池 网络编程入门 UDP通信原理

Java笔记 - 黑马程序员_07(多线程 线程同步 线程池 网络编程入门 UDP通信原理

时间:2022-10-19 14:30:41

相关推荐

Java笔记 - 黑马程序员_07(多线程 线程同步 线程池 网络编程入门 UDP通信原理

1. 实现多线程

1.1 进程

进程:是正在运行的程序

是系统进行资源分配郑调用的独立单位每一个进程都有它自己的内存空间和系统资源

1.2 线程

线程:是进程中的单个顺序控制流,是一条执行路径

单线程:一个进程如果只有一条执行路径,则称为单线程程序多线程:一个进程如果有多条执行路径,则称为多线程程序

举例:

记事本程序扫雷程序

1.3 多线程的实现方式(方式1)

方式1:继承Thread类

定义一个类MyThread继承Thread类在MyThread类中重写run()方法创建MyThread类的对象启动线程

两个小问题:

为什么要重写run() 方法?

因为run() 是用来封装被线程执行的代码

run() 方法和start() 方法的区别?

run() :封装线程执行的代码,直接调用,相当于普通方法的调用

start():启动线程;然后由JVM调用此线程的run()方法

//1.创建线程类继承Thread类package demo_01;public class MyThread extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(i);}}}//2.package demo_01;public class MyThreadDemo {public static void main(String[] args) {//创建线程对象MyThread mt1 = new MyThread();MyThread mt2 = new MyThread();//run() :封装线程执行的代码,直接调用,相当于普通方法的调用/* mt1.run();mt2.run();*///void start()导致此线程开始执行; Java虚拟机调用此线程的run方法。mt1.start();mt2.start();}}

1.4 设置和获取线程名称

Thread类中设置和获取线程名称的方法

void setName(String name):将此线程的名称更改为等于参数nameString getName() :返回此线程的名称通过构造方法也可以设置线程名称

如何获取main() 方法所在的线程名称?

public static Thread currentThread() 返回对当前正在执行的线程对象的引用

//1.package demo_02;public class MyThread extends Thread{public MyThread() {}public MyThread(String name) {super(name);}@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName()+":"+i);}}}//2.package demo_02;/*Thread类中设置和获取线程名称的方法void setName(String name):将此线程的名称更改为等于参数nameString getName() :返回此线程的名称*/public class MyThreadDemo {public static void main(String[] args) {MyThread mt1 = new MyThread();MyThread mt2 = new MyThread();//void setName(String name) 将此线程的名称更改为等于参数 namemt1.setName("高铁");mt2.setName("飞机");//Thread(String name)分配一个新的Thread对象// MyThread mt1 = new MyThread("高铁");// MyThread mt2 = new MyThread("飞机");mt1.start();mt2.start();//static Thread currentThread()返回对当前正在执行的线程对象的引用System.out.println(Thread.currentThread().getName()); //main}}

1.5 线程调度

线程有两种调度模型

分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU时间片相对多一些Java使用的是抢占式调度模型

假如计算机只有一个CPU,那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,

才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的

Thread类中设置和获取线程优先级的方法

public final int getPriority() :返回此线程的优先级public final void setPriority(int newPriority):更改此线程的优先级 线程默认优先级是5;线程优先级的范围是:1-10线程优先级高仅仅表示线程获取的CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果

//1.package demo_03;public class ThreadPriority extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName()+":"+i);}}}//2.package demo_03;public class ThreadPriorityDemo {public static void main(String[] args) {//创建线程对象ThreadPriority tp1 = new ThreadPriority();ThreadPriority tp2 = new ThreadPriority();ThreadPriority tp3 = new ThreadPriority();tp1.setName("高铁");tp2.setName("飞机");tp3.setName("汽车");//public final int getPriority()返回此线程的优先级/*System.out.println(tp1.getPriority()); //5System.out.println(tp2.getPriority()); //5System.out.println(tp3.getPriority()); //5*///void setPriority(int newPriority)更改此线程的优先级/*System.out.println(Thread.MAX_PRIORITY); //10System.out.println(Thread.MIN_PRIORITY); //1System.out.println(Thread.NORM_PRIORITY); //5*///设置正确的优先级tp1.setPriority(5);tp2.setPriority(10);tp3.setPriority(1);//启动线程tp1.start();tp2.start();tp3.start();}}

1.6 线程控制

static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数

//1.package demo_04;public class ThreadSleep extends Thread {public ThreadSleep() {}public ThreadSleep(String name) {super(name);}@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName() + ":" + i);try {//static void sleep(long millis)使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}}//2.package demo_04;//static void sleep(long millis)使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性public class ThreadSleepDemo {public static void main(String[] args) {ThreadSleep ts1 = new ThreadSleep("曹操");ThreadSleep ts2 = new ThreadSleep("刘备");ThreadSleep ts3 = new ThreadSleep("孙权");ts1.start();ts2.start();ts3.start();}}

void join() 等待这个线程死亡

//1.package demo_04;public class ThreadJoin extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(getName()+":"+i);}}}//2.package demo_04;//void join() 等待这个线程死亡public class ThreadJoinDemo {public static void main(String[] args) {//创建线程类对象ThreadJoin tj1 = new ThreadJoin();ThreadJoin tj2 = new ThreadJoin();ThreadJoin tj3 = new ThreadJoin();tj1.setName("张三");tj2.setName("李四");tj3.setName("王五");tj1.start();try {//void join() 等待这个线程死亡tj1.join();} catch (InterruptedException e) {e.printStackTrace();}tj2.start();tj3.start();}}

void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出

//1.package demo_04;public class ThreadDaemon extends Thread{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println();}}}//2.package demo_04;//void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出public class ThreadDaemonDemo {public static void main(String[] args) {ThreadDaemon td1 = new ThreadDaemon();ThreadDaemon td2 = new ThreadDaemon();td1.setName("关羽");td1.setName("张飞");//设置主线程为刘备Thread.currentThread().setName("刘备");//设置守护线程td1.setDaemon(true);td2.setDaemon(true);for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+":"+i);}}}

1.7 线程生命周期

1.8 多线程的实现方式(方式2)

方式2:实现Runnable接口

定义一个类MyRunnable实现Runnablef接口在MyRunnable类中重写run()方法创建MyRunnable类的对象创建Thread类的对象,把MyRunnable对像作为构造方法的参数启动线程

多线程的实现方案有两种

继承Thread类实现Runnable接口

相比继承Thread类,实现Runnablef接口的好处

避免了Java单继承的局限性适合多个相同程序的代码去处理同一个资源的情况,把线程和程序的代码、数据有效分离,较好的体现了面向对象的设计思想

//1.package demo_05;public class MyRunnable implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName()+":"+i);}}}//2.package demo_05;/*方式2:实现Runnable接口1.定义一个类MyRunnable:实现Runnablef接口2.在MyRunnable类中重写run()方法3.创建MyRunnable类的对象4.创建Thread类的对象,把MyRunnable对像作为构造方法的参数5.启动线程*/public class MyRunnableDemo {public static void main(String[] args) {//创建MyRunnable类的对象MyRunnable mr = new MyRunnable();//创建Thread类的对象,把MyRunnable对像作为构造方法的参数//public Thread(Runnable target)// Thread t1 = new Thread(mr);// Thread t2 = new Thread(mr);//public Thread(Runnable target, String name)Thread t1 = new Thread(mr,"高铁");Thread t2 = new Thread(mr,"飞机");//启动线程t1.start();t2.start();}}

2. 线程同步

案例:卖票

需求:某电影院目前正在上映国产大片,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票

思路:

①定义一个类SellTicket实现Runnablef接口,里面定义一个成员变量:private int tickets=100;

②在SellTicket类中重写run() 方法实现卖票,代码步骤如下:

A:判断票数大于0,就卖票,并告知是哪个窗口卖的

B:卖了票之后,总票数要减1

C:票没有了,也可能有人来问,所以这里用死循环让卖票的动作一直执行

③定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下

A:创建SellTicket类的对象

B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称

C:启动线程

//1.定义一个类SellTicket实现Runnable接口package demo_06;public class SellTicket implements Runnable {private int tickets = 100;@Overridepublic void run() {while (true) {if (tickets > 0) {System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--;}}}}//2.定义一个测试类SellTicketDemo,里面有main方法package demo_06;/*定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下A:创建SellTicket类的对象B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称C:启动线程*/public class SellTicketDemo {public static void main(String[] args) {//创建SellTicket类的对象SellTicket st = new SellTicket();//创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称Thread th1 = new Thread(st,"窗口1");Thread th2 = new Thread(st,"窗口2");Thread th3 = new Thread(st,"窗口3");//启动线程th1.start();th2.start();th3.start();}}

2.1 买票案例的思考

刚才讲解了电影院卖票程序,好像没有什么问题。但是在实际生活中,售票时出票也是需要时间的所以,在出售一张票的时候,需要一点时间的延迟,接下来我们去修改卖票程序中卖票的动作:每次出票时间100毫秒,用sleep() 方法实现

卖票出现了问题:

相同的票出现了多次出现了负数的票

问题原因:

线程执行的随机性导致的

//1.package demo_06;public class SellTicket implements Runnable {private int tickets = 100;@Overridepublic void run() {//相同的票出现了多次while (true) {/* if (tickets > 0) {//通过sleep() 方法来模拟出票时间try {Thread.sleep(1000);//t1线程休息1000毫秒//t2线程抢到cpu执行权,t2线程开始执行,执行到这里,t2休息1000毫秒//t3线程抢到cpu执行权,t3线程开始执行,执行到这里,t3休息1000毫秒} catch (InterruptedException e) {e.printStackTrace();}//按照线程顺序醒来//t1抢到线程执执行权,在控制台输出:窗口1正在出售第100张票System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");//t2抢到线程执执行权,在控制台输出:窗口2正在出售第100张票//t3抢到线程执执行权,在控制台输出:窗口3正在出售第100张票tickets--;//如果这三个线程按照顺序,执行到这里执行了3次--操作,最终票变成了97}*///出现负数票的情况if (tickets > 0) {//通过sleep() 方法来模拟出票时间try {Thread.sleep(10);//t1线程休息1000毫秒//t2线程抢到cpu执行权,t2线程开始执行,执行到这里,t2休息1000毫秒//t3线程抢到cpu执行权,t3线程开始执行,执行到这里,t3休息1000毫秒} catch (InterruptedException e) {e.printStackTrace();}//假设线程按照顺序醒过来//t1抢到了CPU的执行权,在控制台输出:窗口1正在出售第1张票//假设t1继续拥有cPU的执行权,就会执行tickets--;操作,tickets=0;//t2抢到了CPU的执行权,在控制台输出:窗口1正在出售第0张票//假设t2继续拥有CPU的执行权,就会执行tickets--;操作,tickets=-1;//t3抢到了CPU的执行权,在控制台输出:窗口3正在出售第-1张票//假设t2继续拥有CPU的执行权,就会执行tickets-;操作,tickets=-2;System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--;}}}}//2.package demo_06;/*定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下A:创建SellTicket类的对象B:创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称C:启动线程*/public class SellTicketDemo {public static void main(String[] args) {//创建SellTicket类的对象SellTicket st = new SellTicket();//创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称Thread th1 = new Thread(st,"窗口1");Thread th2 = new Thread(st,"窗口2");Thread th3 = new Thread(st,"窗口3");//启动线程th1.start();th2.start();th3.start();}}

2.2 卖票案例数据安全问题解决

为什么出现问题?(这也是我们判断多线程程序是否会有数据安全问题的标准)

是否是多线程环境是否有共享数据是否有多条语句操作共享数据

如何解决多线程安全问题呢?

基本思想:让程序没有安全问题的环境

怎么实现呢?

把多条语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可Java提供了同步代码块的方式来解决(如下2.3)

2.3 同步代码块

锁多条语句操作共享数据,可以使用同步代码块实现

格式:

synchronized(任意对象){多条语句操作共享数据的代码}

synchronized(任意对象):就相当于给代码加锁了,任意对象就可以看成是一把锁

同步的好处和弊端:

好处:解决了多线程的数据安全问题弊端:当线程很多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率

//1.package demo_06;public class SellTicket implements Runnable {private int tickets = 100;private Object obj = new Object();@Overridepublic void run() {while (true) {synchronized (obj){//t1进来后就会把这段代码锁起来if (tickets > 0) {try {Thread.sleep(100);//t1线程休息100毫秒} catch (InterruptedException e) {e.printStackTrace();}//窗口正在出售第100张票System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--;}}//t1出来了,这段代码就被释放了}}}//2.package demo_06;public class SellTicketDemo {public static void main(String[] args) {//创建SellTicket类的对象SellTicket st = new SellTicket();//创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称Thread th1 = new Thread(st,"窗口1");Thread th2 = new Thread(st,"窗口2");Thread th3 = new Thread(st,"窗口3");//启动线程th1.start();th2.start();th3.start();}}

2.4 同步方法

同步方法:就是把synchronized关键字加到方法上

格式:

修饰符 synchronized 返回值类型 方法名(方法参数){}

同步方法的锁对象是什么呢?

this

同步静态方法:就是把synchronized关键字加到静态方法上

格式:

修饰符 static synchronized 返回值类型 方法名(方法参数){}

同步静态方法的锁对象是什么呢?

类名.class

//1.package demo_06;public class SellTicket implements Runnable {private static int tickets = 100;private Object obj = new Object();private int x = 0;@Overridepublic void run() {while (true) {if (x % 2 == 0) {//synchronized (obj) {//synchronized (this) { //同步方法锁synchronized (SellTicket.class) {//静态同步方法锁//t1进来后就会把这段代码锁起来if (tickets > 0) {try {Thread.sleep(10);//t1线程休息100毫秒} catch (InterruptedException e) {e.printStackTrace();}//窗口正在出售第100张票System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--;}}} else {sellticks();}x++;}}/*private void sellticks() {synchronized (obj) {//t1进来后就会把这段代码锁起来if (tickets > 0) {try {Thread.sleep(100);//t1线程休息100毫秒} catch (InterruptedException e) {e.printStackTrace();}//窗口正在出售第100张票System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--;}}}*///同步方法/*private synchronized void sellticks() {if (tickets > 0) {try {Thread.sleep(100);//t1线程休息100毫秒} catch (InterruptedException e) {e.printStackTrace();}//窗口正在出售第100张票System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--;}}*///静态同步方法private static synchronized void sellticks() {if (tickets > 0) {try {Thread.sleep(100);//t1线程休息100毫秒} catch (InterruptedException e) {e.printStackTrace();}//窗口正在出售第100张票System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--;}}}//2.package demo_06;public class SellTicketDemo {public static void main(String[] args) {//创建SellTicket类的对象SellTicket st = new SellTicket();//创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称Thread th1 = new Thread(st,"窗口1");Thread th2 = new Thread(st,"窗口2");Thread th3 = new Thread(st,"窗口3");//启动线程th1.start();th2.start();th3.start();}}

2.5 线程安全的类

StringBuffer

线程安全,可变的字符序列从版本JDK5开始,被StringBuilder替代。通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步

Vector

从Java2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。与新的集合实现不同,Vector被同步。如果不需要线程安全的实现,建议使用ArrayList代替Vector

Hashtable

该类实现了一个哈希表,它将键映射到值。任何非null对像都可以用作键或者值从Java2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。

与新的集合实现不同,Hashtable被同步。如果不需要线程安全的实现,建议使用HashMap代替Hashtable

package demo_07;import java.util.*;/*线程的安全类:StringBufferVertorHashtable*/public class ThreadDemo {public static void main(String[] args) {StringBuffer sb2 = new StringBuffer();StringBuilder sb = new StringBuilder();Vector<String> v = new Vector<String>();ArrayList<String> array = new ArrayList<String>();Hashtable<String, String> ht = new Hashtable<String, String>();HashMap<String, String> hm = new HashMap<String, String>();//public static <T> List<T> synchronizedList(List<T> list)返回由指定列表支持的同步(线程安全)列表List<String> lise = Collections.synchronizedList(new ArrayList<String>());}}

2.6 Lock锁

虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock实现提供比使用synchronized() 方法和语句可以获得更广泛的锁定操作

Lock中提供了获得锁和释放锁的方法:

void lock() :获得锁void unlock() :释放锁

Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

ReentrantLock的构造方法:

ReentrantLock() :创建一个ReentrantLock的实例

//1.package demo_08;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class SellTicket implements Runnable {private int tickets = 100;private Lock lock = new ReentrantLock();@Overridepublic void run() {while (true) {try {lock.lock();if (tickets > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");tickets--;}} finally {lock.unlock();}}}}//2.package demo_08;public class SellTicketDemo {public static void main(String[] args) {SellTicket st = new SellTicket();Thread th1 = new Thread(st,"窗口1");Thread th2 = new Thread(st,"窗口2");Thread th3 = new Thread(st,"窗口3");th1.start();th2.start();th3.start();}}

3 线程池

Java提供了线程池技术,让线程可以重复利用,解决线程频繁创建和销毁的问题,提高运行效率。

创建线程池

Executors类是线程池的工具类,通过Executors.工具类可以创建线程池。

使用线程池

ExecutorService代表线程池,该类中提供了submit方法用于处理提交的任务。调用submit(任务)方法时,线程池会分配池中空闲的线程去执行对应的任务。

创建任务的两种方式:

实现Runnable:接口,重写run方法。实现Callable<返回值类型>接口,重写call方法。

3.1 线程池Runable方法(案例)

//1.package com.demo_2线程池Runnable接口;public class MyRunnable implements Runnable{@Overridepublic void run() {String name = Thread.currentThread().getName();System.out.println(name+"执行了...");}}//2.测试类package com.demo_2线程池Runnable接口;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class Demo {public static void main(String[] args) {//创建线程数量为3的线程池ExecutorService pool = Executors.newFixedThreadPool(3);//使用线程池提交任务// submit(任务)MyRunnable mr = new MyRunnable();pool.submit(mr);pool.submit(mr);pool.submit(mr);pool.submit(mr);//线程池中的线程都是出于活跃状态,代码不会停止//把线程池关闭,线程会全部销毁//pool.shutdown(); //不要关闭}}

3.2 线程池Callable方法(案例)

//1.package com.demo_3Callable方法;import java.util.Random;import java.util.concurrent.Callable;public class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {String name = Thread.currentThread().getName();System.out.println(name+"执行了....");Random r = new Random();int i = r.nextInt(10);System.out.println(i);return i;}}//2.测试类package com.demo_3Callable方法;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;public class CallableDemo {public static void main(String[] args) throws Exception {//创建线程池ExecutorService pool = Executors.newFixedThreadPool(3);//提交MyCallable任务MyCallable mc = new MyCallable();//线程池执行任务,并把返回值装到future中Future<Integer> f = pool.submit(mc);//通过future的get方法,获取返回值Integer a = f.get();System.out.println("a="+a);//关闭线程池(不建议使用,线程池关闭线程里的东西全部销毁)pool.shutdown();}}

案例:使用线程池完成求和计算

需求:使用线程池方式创建两个线程任务:分段计算1~20000之间的数字和。

1.线程1计算1~10000之间的数字和,并返回结果。

2.线程2计算10001~20000之间的数字和,并返回结果。

3.提交任务,获取计算结果进行合并,打印最终结果。

分析:因为要返回求和结果,使用Callable来实现。

package com.demo_3Callable方法;import java.util.concurrent.*;public class Demo {public static void main(String[] args) throws Exception {//创建线程池ExecutorService pool = Executors.newFixedThreadPool(3);//线程池任务(计算1-10000)Future<Integer> f1 = pool.submit(new Callable<Integer>() {@Overridepublic Integer call() throws Exception {String name = Thread.currentThread().getName();System.out.println(name + "线程执行了......");int sum = 0;for (int i = 1; i <= 10000; i++) {sum += i;}return sum;}});//线程池任务(计算10001-20000)Future<Integer> f2 = pool.submit(new Callable<Integer>() {@Overridepublic Integer call() throws Exception {String name = Thread.currentThread().getName();System.out.println(name + "线程执行了......");int sum = 0;for (int i = 1; i <= 10000; i++) {sum += i;}return sum;}});//获取返回值Integer result1 = f1.get();Integer result2 = f2.get();System.out.println("最终的结果为" + result1 + result2);}}

4. 网络编程入门

4.1 网络编程概述

计算机网络

是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统

网络编程

在网络通信协议下,实现网络互连的不同计算机上运行的程序间可以进行数据交换

4.2 网络编程三要素

IP地址

要想让网络中的计算机能够互相通信,必须为每台计算机指定一个标识号,通过这个标识号来指定要接收数据的计算机和识别发送的计算机,而P地址就是这个标识号。也就是设备的标识

端口

网络的通信,本质上是两个应用程序的通信。每台计算机都有很多的应用程序,那么在网络通信时,如何区分这些应用程序呢?如果说P地址可以唯一标识网络中的设备,那么端口号就可以唯一标识设备中的应用程序了。也就是应用程序的标识

协议

通过计算机网络可以使多台计算机实现连接,位于同一个网络中的计算机在进行连接和通信时需要遵守一定的规则,这就好比在道路中行驶的汽车一定要遵守交通规呗则一样。在计算机网络中,这些连接和通信的规则被称为网络通信协议,它对数据的传输格式、传输速率、传输步骤等做了统一规定,通信双方必须同时遵守才能完成数据交换。常见的协议有UDP协议和TCP协议

4.3 IP地址

IP地址:是网络中设备的唯一标识

P地址分为两大类

IPv4:是给每个连接在网络上的庄机分配一个32bi地址。按照TCP八P规定,P地址用二进制来表示,每个P地址长32bit,也就是4个字节。例破如一个采用二进制形式的P地址是“11000000101010000000000101000010°”,这么长的地址,处理起来也太费劲了。为了方便使用,P地址经常被写成十进制的形式,中间使用符号“”分隔不同的字节。于是,上面的1P地址可以表示为"192.168.1.66”。P地址的这种表示法叫做“点分十进制表示法”,这显然比1和0容易记忆得多IPV6:由于互联网的蓬勃发展,P地址的需求量愈来愈大,但是网络地址资源有限,使得P的分配越发紧张。为了扩大地址空间,通过Pv6重新定义地址空间,采用128位地址长度,每16个字节一组,分成8组十六进制数,这样就解决了网络地址资源数量不够的问题

常用命令:

ipconfig:查看本机IP地址ping IP地址:检查网络是否连通

特殊IP地址:

127.0.0.1:是回送地址,可以代表本机地址,般用来测试使用

4.4 InetAddress的使用

为了方便我们对lP地址的获取和操作,Java提供了一个类InetAddress供我们使用

InetAddress:此类表示Internet协议 (IP) 地址

package demo_01;/*InetAddress:此类表示Internet协议 (IP) 地址static InetAddress getByName(String host)确定主机名称的P地址。主机名称可以是机器名称,也可以是P地址String getHostName()获取此P地址的主机名String getHostAddress()返回文本显示中的IP地址字符串*/import java.io.IOException;import .InetAddress;public class InetAddressDemo {public static void main(String[] args) throws IOException {//static InetAddress getByName(String host)确定主机名称的IP地址。主机名称可以是机器名称,也可以是IP地址InetAddress address = InetAddress.getByName("192.168.31.81");//String getHostName()获取此P地址的主机名String name = address.getHostName();//String getHostAddress()返回文本显示中的IP地址字符串String ip = address.getHostAddress();System.out.println("主机名:"+name);System.out.println("IP地址:"+ip);}}

4.5 端口

端口:设备上应用程序的唯一标识

端口号:用两个字节表示的整数,它的取值范围是065535。其中,01023之间的端口号用于一些知名的网络服务和应用,普通的应用程序需要使用1024以上的端口号。如果端口号被另外一个服务或应用所占用,会导致当前程序启动失败

4.6 协议

协议:计算机网络中,连接和通信的规则被称为网络通信协议

UDP协议:

用户数据报协议(User Datagram Protocol)UDP是无连接通信协议,即在数据传输时,数据的发送端和接收端不建立逻辑连接。简单来说,当一台计算机向另外一台计算机发送数据时,发送端不会确认接收端是否存在,就会发出数据,同样接收端在收到数据时,也不会向发送端反馈是否收到数据。由于使用UDP协议消耗资源小,通信效率高,所以通常都会用于音频、视频和普通数据的传输例如视频会议通常采用UDP协议,因为这种情况即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。但是在使用UDP协议传送数据时,由于UDP的面向无连接性,不能保证数据的完整性,因此在传输重要数据时不建议使用UDP协议

TCP协议:

传输控制协议(Transmission Control Protocol)TCP协议是面向连接的通信协议,即传输数据之前,在发送端和接收总建立逻辑连接,然后再传输数据,

它提供了两台计算机之间可靠无差错的数据传输。在TCP连接中必须要明确客户端与服务器端,由客户端

向服务端发出连接请求,每次连接的创建都需要经过“三次握手”三次握手:TCP协议中,在发送数据的准备阶段,客户端与服务器之间的三次交互,以保证连接的可靠

第一次握手,客户端向服务器端发出连接请求,等待服务器确认

第二次握手,服务器端向客户端回送一个响应,通知客户端收到了连接请求

第三次握手,客户端再次向服务器端发送确认信息,确认连接完成三次握手,连接建立后,客户端和服务器就可以开始进行数据传输了。由于这种面向连接的特性,TCP协议可以保证传输数据的安全,所以应用十分广泛。例如上传文件、下载文件、浏览网页等

5. UDP通信原理

5.1 UDP通信原理

UDP协议是一种不可靠的网络协议,它在通信的两端各建立一个Socket对象,但是这两个Socket只是发送,接收数据的对象因此对于基于UDP协议的通信双方而言,没有所谓的客户端和服务器的概念Java提供了DatagramSocket类作为基于UDP协议的Socket

5.2 UDP发送数据

发送数据的步骤

①创建发送端的Socket对象(DatagramSocket)

DatagramScoket()

②创建数据,并把数据打包

DatagramPacket(byte[]buf,int length,InetAddress address,int port)

③调用DatagramSocket对象的方法发送数据

void send(DatagramPacket p)

④关闭发送端

void close()

package demo_02;/*发送数据的步骤:创建发送端的Socket对象(DatagramSocket)创建数据,并把数据打包调用DatagramSocket对象的方法发送数据关闭发送端*/import java.io.IOException;import .DatagramPacket;import .DatagramSocket;import .InetAddress;public class SendDemo {public static void main(String[] args) throws IOException {//创建发送端的Socket对象(DatagramSocket)//DatagramSocket()//构造数据报套接字并将其绑定到本地主机上的任何可用端口DatagramSocket ds = new DatagramSocket();//创建数据,并把数据打包//DatagramPacket(byte[] buf, int length, InetAddress address, int port)//构造用于发送长度的分组的数据报包 length指定主机上到指定的端口号byte[] bys = "hello,java".getBytes();DatagramPacket dp = new DatagramPacket(bys,bys.length, InetAddress.getByName("192.168.31.81"),10010);//调用DatagramSocket对象的方法发送数据//void send(DatagramPacket p)从此套接字发送数据报包 ds.send(dp);//关闭发送端ds.close();}}

5.3 UDP接收数据

接收数据的步骤

①创建接收端的Socket对象(DatagramSocket)

DatagramSocket(int port)

②创建一个数据包,用于接收数据

DatagramPacket(byte[]buf,int length)

③调用DatagramSocket对象的方法接收数据

void receive(DatagramPacket p)

④解析数据包,并把数据在控制台显示

byte[] getData()int getLength()

⑤关闭接收端

void close()

package demo_02;/*接收数据的步骤:创建接收端的Socket对象(DatagramSocket)创建一个数据包,用于接收数据调用DatagramSocket对象的方法接收数据解析数据包,并把数据在控制台显示关闭接收端*/import java.io.IOException;import .DatagramPacket;import .DatagramSocket;public class ReceiveDemo {public static void main(String[] args) throws IOException {//创建接收端的Socket对象(DatagramSocket)DatagramSocket ds = new DatagramSocket(10010);//创建一个数据包,用于接收数据byte[] bys = new byte[1024];DatagramPacket dp = new DatagramPacket(bys,bys.length);//调用DatagramSocket对象的方法接收数据ds.receive(dp);//解析数据包,并把数据在控制台显示byte[] datas = dp.getData();int len = dp.getLength();System.out.println("数据是:"+new String(datas,0,len));//关闭接收端ds.close();}}

5.4 UDP通信程序练习

按照下面的要求实现程序:

UDP发送数据:数据来自于键盘录入,直到输入的数据是886,发送数据结束UDP接收数据:因为接收端不知道发送端什么时候停止发送,故采用死循环接收

//1.发送端package demo_03;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import .DatagramPacket;import .DatagramSocket;import .InetAddress;public class SendDemo {public static void main(String[] args) throws IOException {//创建发送端的Scoket对象(DatagramScoket());DatagramSocket ds = new DatagramSocket();//自己封装键盘录入数据BufferedReader br = new BufferedReader(new InputStreamReader(System.in));String line;while ((line=br.readLine())!=null){//输入的数据为886,发送数据结束if ("886".equals(line)){break;}//创建数据并把数据打包byte[] bys = line.getBytes();DatagramPacket dp = new DatagramPacket(bys,bys.length, InetAddress.getByName("192.168.31.81"),10010);//调用DagramScoket对象的方法发送数据ds.send(dp);//关闭发送端// ds.close();}}}//2.服务器端package demo_03;import java.io.IOException;import .DatagramPacket;import .DatagramSocket;import .SocketException;public class ServerDemo {public static void main(String[] args) throws IOException {//创建结束端的Scoket对象(DatagramScoket)DatagramSocket ds = new DatagramSocket(10010);while (true){//创建数据包用于接收数据byte[] bys = new byte[1024];DatagramPacket dp = new DatagramPacket(bys,bys.length);//调用DatagramScoket对象的方法接收数据ds.receive(dp);//解析数据包,并把数据输出在控制台System.out.println("数据是:"+new String(dp.getData(),0,dp.getLength()));}//关闭接收端// ds.close();}}

6. TCP通信原理

6.1 TCP通信原理

TCP通信协议是一种可靠的网络协议,它在通信的两端各建立一个Socket对象,从而在通信的两端新形成网络虚拟链路,一旦建立了虚拟的网络链路,两端的程序就河以通过虚拟链路进行通信Java对基于TCP协议的的网络提供了良好的封装,使用Socket对象来代表两端的通信端口,并通过Socket产生IO流来进行网络通信Java为客户端提供了Socket类,为服务器端提供了ServerSocket类

6.2 TCP发送数据

发送数据的步骤:

①创建客户端的Socket对象(Socket)

Socket(String host,int port)

②获取输出流,写数据

OutputStream getOutputStream()

③释放资源

void close()

package TCPDemo_04;/*发送数据的步骤创建客户端的Socket对象(Socket)获取输出流,写数据科放资源*/import java.io.IOException;import java.io.OutputStream;import .InetAddress;import .Socket;public class ClientDemo {public static void main(String[] args) throws IOException {//创建客户端的Socket对象(Socket)//Socket(InetAddress address, int port)创建流套接字并将其连接到指定IP地址的指定端口号// Socket s = new Socket(InetAddress.getByName("192.168.31.81"),10010);//Socket(String host, int port)创建流套接字并将其连接到指定主机上的指定端口号Socket s= new Socket("192.168.31.81",10010);//获取输出流,写数据//OutputStream getOutputStream()返回此套接字的输出流OutputStream os = s.getOutputStream();os.write("hello,java".getBytes());//释放资源s.close();}}

6.3 TCP接受数据

接收数据的步骤:

①创建服务器端的Socket对象(ServerSocket)

ServerSocket(int port)

②监听客户端连接,返回一个Socket对象

Socket accept()

③获取输入流,读数据,并把数据显示在控制台

InputStream getlnputStream()

④释放资源

void close()

package TCPDemo_04;/*接收数据的步骤:创建服务器端的Socket对象(ServerSocket)监听客户端连接,返回一个Sockety对象获取输入流,读数据,并把数据显示在控制台释放资源*/import java.io.IOException;import java.io.InputStream;import .ServerSocket;import .Socket;public class Serverdemo {public static void main(String[] args) throws IOException {//创建服务器端的Socket对象(ServerScoket)//ServerSocket(int port)创建绑定到指定端口的服务器套接字ServerSocket ss = new ServerSocket(10010);//监听客户端连接,返回一个Sockety对象Socket s = ss.accept();//获取输入流,读数据,并把数据显示在控制台InputStream is = s.getInputStream();byte[] bys = new byte[1024];int len = is.read(bys);String data = new String(bys,0,len);System.out.println("数据是:"+data);//释放资源ss.close();s.close();}}

6.4 TCP通信程序练习

练习1

客户端:发送数据,接收服务器反馈服务端:接收数据,给出反馈

//服务器端package TcpPracticeDemo_01;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import .ServerSocket;import .Socket;//服务端:接收数据,给出反馈public class ServerDemo {public static void main(String[] args) throws IOException {//创建服务器Socket对象(ServerSocket)ServerSocket ss = new ServerSocket(10010);//监听客户端连接,返回Socket对象Socket s = ss.accept();//获取输入流,读数据,并把数据显示在控制台InputStream is = s.getInputStream();byte[] bys = new byte[1024];int len = is.read(bys);String data = new String(bys,0,len);System.out.println("服务器"+data);/*int len;while ((len = is.read(bys)) != -1) {String data = new String(bys,0,len);System.out.println("服务器"+data);}*///给出反馈OutputStream os = s.getOutputStream();os.write("over".getBytes());//释放资源ss.close();}}//客户端package TcpPracticeDemo_01;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import .Socket;//客户端:发送数据,接收服务器反馈public class ClientDemo {public static void main(String[] args) throws IOException {//创建客户端Socket对象Socket ss = new Socket("192.168.31.81",10010);//获取输出流,写数据OutputStream os = ss.getOutputStream();os.write("hello,java".getBytes());//接受服务器反馈InputStream is = ss.getInputStream();byte[] bys = new byte[1024];int len;while ((len = is.read(bys)) != -1) {System.out.println("客户端:" + new String(bys, 0, len));}//释放资源ss.close();}}

练习2

客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束服务器:接收到的数据在控制台输出

//服务器端package TPracticeDemo_02;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import .ServerSocket;import .Socket;//服务器:接收到的数据在控制台输出public class ServerDemo {public static void main(String[] args) throws IOException {//创建服务器端Socket对象ServerSocket ss = new ServerSocket(10010);//监听客户端连接,返回对应的Socket对象Socket s = ss.accept();//获取输入流BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));String line;while ((line = br.readLine()) != null) {String data = new String(line);System.out.println("服务器:"+data);}//释放资源ss.close();}}//客户端package TPracticeDemo_02;import java.io.*;import .InetAddress;import .Socket;//客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束public class ClientDemo {public static void main(String[] args) throws IOException {//创建客户端Socket对象Socket s= new Socket(InetAddress.getByName("192.168.31.81",10010);//数据来自键盘录入,输入886,发送结束BufferedReader be = new BufferedReader(new InputStreamReader(System.in));//封装输出数据流BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));String line;while ((line=be.readLine())!=null){if ("886".equals(line)){break;}bw.write(line);bw.newLine();bw.flush();}//释放资源s.close();}}

练习3

客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束服务器:接收到的数据写入文本文件

//服务器端package TPracticeDemo_03;import com.sun.source.tree.WhileLoopTree;import java.io.*;import .ServerSocket;import .Socket;//- 服务器:接收到的数据写入文本文件public class ServerDemo {public static void main(String[] args) throws IOException {//创建服务器Socket对象(ServerSocket)ServerSocket ss = new ServerSocket(10010);//监听客户端连接,返回Socket对象Socket s = ss.accept();//创建输入流,接收数据BufferedReader data = new BufferedReader(new InputStreamReader(s.getInputStream()));//创建输出流对象BufferedWriter bw = new BufferedWriter(new FileWriter("myInet\\java.txt"));String line;while ((line = data.readLine()) != null) {bw.write(line);bw.newLine();bw.flush();}//释放资源ss.close();bw.close();}}//客户端package TPracticeDemo_03;import java.io.*;import .InetAddress;import .Socket;//- 客户端:数据来自于键盘录入,直到输入的数据是886,发送数据结束public class ClientDemo {public static void main(String[] args) throws IOException {//创建客户端Socket对象Socket s = new Socket(InetAddress.getByName("192.168.31.81"),10010);//创建输入流,键盘录入数据,直到886结束BufferedReader br = new BufferedReader(new InputStreamReader(System.in));//创建输出流BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));String line;while ((line = br.readLine()) != null) {if ("886".equals(line)) {break;}bw.write(line);bw.newLine();bw.flush();}//释放资源s.close();}}

练习4

客户端:数据来自于文本文件服务器:接收到的数据写入文本文件

//服务器端package TPracticeDemo_04;import java.io.*;import .ServerSocket;import .Socket;//- 服务器:接收到的数据写入文本文件public class ServerDemo {public static void main(String[] args) throws Exception{//创建服务器端Socket对象ServerSocket ss = new ServerSocket(10010);//监听客户端连接,返回Socket对象Socket s = ss.accept();//创建输入流接收数据BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));//创建输出流BufferedWriter bw = new BufferedWriter(new FileWriter("myInet\\java.txt"));String line;while ((line = br.readLine()) != null) {bw.write(line);bw.newLine();bw.flush();}//释放资源ss.close();bw.close();}}//客户端package TPracticeDemo_04;import java.io.BufferedReader;import java.io.BufferedWriter;import java.io.FileReader;import java.io.OutputStreamWriter;import .Socket;//- 客户端:数据来自于文本文件public class ClientDemo {public static void main(String[] args) throws Exception{//创建客户端Socket对象Socket s = new Socket("192.168.31.81",10010);//创建输入流BufferedReader br = new BufferedReader(new FileReader("myInet\\copy.txt"));//创建输出流BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));String line;while ((line = br.readLine()) != null) {bw.write(line);bw.newLine();bw.flush();}//释放资源s.close();br.close();}}

练习5

客户端:数据来自于文本文件,接收服务器反馈服务器:接收到的数据写入文本文件,给出反馈

出现问题:程序一直等待

原因:读数据的方法是阻塞式的

解决办法:自定义结束标记使用shutdownOutput()方法(推荐)

//服务器端package TPracticeDemo_05;import java.io.*;import .ServerSocket;import .Socket;//- 服务器:接收到的数据写入文本文件,给出反馈public class ServerDemo {public static void main(String[] args) throws Exception{//创建服务端Socket对象ServerSocket ss = new ServerSocket(10010);//监听客户端,返回Socket对象Socket s = ss.accept();//创建输入流BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));//创建输出流BufferedWriter bw= new BufferedWriter(new FileWriter("myInet\\copy.txt"));String line;while ((line = br.readLine()) != null) {bw.write(line);bw.newLine();bw.flush();}System.out.println(11111);//创建输出流输出反馈数据OutputStream outputStream = s.getOutputStream();outputStream.write("数据已接收".getBytes());//释放资源ss.close();bw.close();}}//客户端package TPracticeDemo_05;import java.io.*;import .InetAddress;import .Socket;//- 客户端:数据来自于文本文件,接收服务器反馈public class ClientDemo {public static void main(String[] args) throws Exception{//创建客户端Socket对象Socket s = new Socket("192.168.31.81", 10010);//创建输入流BufferedReader br = new BufferedReader(new FileReader("myInet\\java.txt"));//创建输出流BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));String line;while ((line = br.readLine()) != null) {bw.write(line);bw.newLine();bw.flush();}//void shutdownOutput()禁用此套接字的输出流s.shutdownOutput();//创建输入流接收服务器反馈InputStream inputStream = s.getInputStream();byte[] bys = new byte[1024];int len;while ((len=inputStream.read(bys))!=-1){System.out.println(new String(bys,0,len));}//释放资源s.close();br.close();}}

练习6

客户端:数据来自于文本文件,接收服务器反馈服务器:接收到的数据写入文本文件,给出反馈,代码用线程进行封装,为每一个客户端开启一个线程

//1.客户端package TPracticeDemo_06;//- 客户端:数据来自于文本文件,接收服务器反馈import java.io.*;import .Socket;public class ClientDemo {public static void main(String[] args) throws Exception{//创建客户端Socket对象Socket s = new Socket("192.168.31.81",10010);//创建输入流BufferedReader br = new BufferedReader(new FileReader("myInet\\java.txt"));//创建输出流BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));String line;while ((line = br.readLine()) != null) {bw.write(line);bw.newLine();bw.flush();}s.shutdownOutput();//创建输入流接受服务器反馈InputStream inputStream = s.getInputStream();byte[] bys = new byte[1024];int len;while ((len = inputStream.read(bys)) != -1) {System.out.println(new String(bys,0,len));}//释放资源s.close();br.close();}}//2.服务器端package TPracticeDemo_06;import .ServerSocket;import .Socket;//- 服务器:接收到的数据写入文本文件,给出反馈,代码用线程进行封装,为每一个客户端开启一个线程public class ServerDemo {public static void main(String[] args) throws Exception {//创建服务器Socket对象ServerSocket ss = new ServerSocket(10010);while (true) {//监听客户端连接,返回socket对象Socket s = ss.accept();//为每一个客户端开启一个线程new Thread(new ServerThread(s)).start();}}}//3.多线程package TPracticeDemo_06;import java.io.*;import .Socket;public class ServerThread implements Runnable {private Socket s;public ServerThread(Socket s) {this.s = s;}@Overridepublic void run() {//接收数据写到文本文件try {BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));//解决命名冲突问题int count = 0;File file = new File("myInet\\copy"+count+".txt");while (file.exists()) {count++;file = new File("myInet\\copy"+count+".txt");}BufferedWriter bw= new BufferedWriter(new FileWriter(file));String line;while ((line = br.readLine()) != null) {bw.write(line);bw.newLine();bw.flush();}//给出反馈OutputStream outputStream = s.getOutputStream();outputStream.write("服务器已成功接收数据".getBytes());} catch (Exception e){e.printStackTrace();}}}

commons-io工具类的使用

commons-io概述

commons-io是apache开源基金组织提供的一组有关IO操作的类库,可以挺提高lO功能开发的效率commons-io工具包提供了很多有关IO操作的类。有两个主要的类FileUtils, IOUtils

FileUtils主要有如下方法:

commons-io使用步骤:

导入commons-io-2.6.jar在项目中创建一个文件夹:lib将commons-io-2.6.jar文件复制到lib文件夹在jar文件上点右键,选择Add as Library->点击OK在类中导包使用

package com.itheima.demo08_commons_io使用;import mons.io.FileUtils;import mons.io.IOUtils;import java.io.File;import java.io.IOException;public class Demo08 {public static void main(String[] args) throws IOException {// 1.读取文件中内容返回字符串。参数1:文件对象,参数2:字符集String s = FileUtils.readFileToString(new File("day_12/src/com\\itheima\\demo08_commons_io使用\\Demo08.java"), "utf8");System.out.println(s);// 2.复制文件。参数1:源文件,参数2:目标文件FileUtils.copyFile(new File("day_12/src/com\\itheima\\demo08_commons_io使用\\Demo08.java"),new File("D:/temp/temp.txt"));// 3.复制文件夹。参数1:源目录,参数2:目标目录 (连同子孙目录下的文件和目录一起复制)FileUtils.copyDirectoryToDirectory(new File("day_12/src/com/itheima"),new File("D:/temp"));}}

更多内容请访问博主博客:逸乐的博客 - 今晚一起吃火锅

文章如有纰漏请指出,整理不易多多包涵。

Java后续笔记将持续更新…

Java笔记 - 黑马程序员_07(多线程 线程同步 线程池 网络编程入门 UDP通信原理 TCP通信原理 commons-io工具类)

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。