Java基础入门20:特殊文件、日志技术、多线程、网络通信

2024-08-26 13:20

本文主要是介绍Java基础入门20:特殊文件、日志技术、多线程、网络通信,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

特殊文件

properties属性文件

Properties是一个Mp集合(键值对集合),但是我们一般不会当集合使用。

核心作用:Properties是用来代表属性文件的,通过Properties可以读写属性文件里的内容。

使用Properties把键值对数据写出到属性文件里去

先创建一个.txt文件

package com.itchinajie.d1_properties;import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.Properties;
import java.util.Set;/*
* 目标:掌握使用Properties类读取属性文件中的键值对信息
* */
public class PropertiesTest1 {public static void main(String[] args) throws Exception {//1、创建一个Properties的对象出来(键值对集合,空容器)Properties properties = new Properties();System.out.println(properties);//2、开始加载属性文件中的键值对数据到properties对象中去properties.load(new FileReader("properties-xml-log-app\\src\\users.properties"));//3、根据键取值System.out.println(properties.getProperty("赵敏"));System.out.println(properties.getProperty("张无忌"));//4、遍历全部的键和值Set<String> keys = properties.stringPropertyNames();for (String key : keys) {String value = properties.getProperty(key);System.out.println(key + "--->" + value);}properties.forEach((k,v) -> {System.out.println(k + "--->" + v);});}
}
package com.itchinajie.d1_properties;import java.io.FileReader;
import java.io.FileWriter;
import java.util.Properties;
import java.util.Set;/*
* 目标:掌握把键值对数据存储到属性文件中去
* */
public class PropertiesTest2 {public static void main(String[] args) throws Exception {//1、创建Properties对象出来,先用他存储一些键值对数据Properties properties = new Properties();properties.setProperty("张无忌","minmin");properties.setProperty("殷素素","cuishan");properties.setProperty("张翠山","susu");//2、把Properties对象中的键值对数据存入到属性文件中去properties.store(new FileWriter("properties-xml-log-app\\src\\users2.properties"),"i saved many users!");}
}
package com.itchinajie.d1_properties;import java.io.FileReader;
import java.io.FileWriter;
import java.util.Properties;public class PropertiesTest3 {public static void main(String[] args) throws Exception {//目标:读取属性文件,判断是否存在李芳,存在年龄改成18//1、加载属性文件的键值对到程序中来Properties properties = new Properties();//{}//2、开始加载properties.load(new FileReader("properties-xml-log-app\\src\\users.txt"));//3、判断是否包含李芳这个键if (properties.containsKey("李芳")){properties.setProperty("李芳","18");}//4、把properties对象的键值对数据重新写出去到属性文件中去properties.store(new FileWriter("properties-xml-log-app\\src\\users.txt"),"success!");}
}

XML(可扩展标记性语言)文件

XML(全称EXtensible Markup Language,可扩展标记语言)本质是一种数据的格式,可以用来存储复杂的数据结构,和数据关系。

XML的特点

XML中的“<标签名>”称为一个标签或一个元素,一般是成对出现的。

XML中的标签名可以自己定义(可扩展),但必须要正确的嵌套。

XML中只能有一个根标签。

XML中的标签可以有属性。

如果一个文件中放置的是XML格式的数据,这个文件就是XML文件,后缀一般要写成.xml

XML的创建

就是创建一个XML类型的文件,要求文件的后缀必须使用xml,如hello_world.xml

IDEA创建XML文件

XML的语法规则

XML文件的后缀名为:xml,文档声明必须是第一行

XML中可以定义注释信息:<!--注释内容-->

XML中书写”<”、“&”等,可能会出现冲突,导致报错,此时可以用如下特殊字符替代.

XML中可以写一个叫CDATA的数据区:<![CDATA[...内容...]]>,里面的内容可以随便写。

XML的作用和应用场景

本质是一种数据格式,可以存储复杂的数据结构,和数据关系。

应用场景:经常用来做为系统的配置文件,或者作为一种特殊的数据结构,在网络中进行传输。

注意:目前XML文件很少用于数据传输,大多是JSON.

读取XML文件中的数据

解析XML文件

使用程序读取XML文件中的数据

注意:程序员并不需要自己写原始的IO流代码来解析XML,难度较大!也相当繁琐!

其实,有很多开源的,好用的,解析XML的框架,最知名的是:Do4j(第三方研发的)

package com.itchinajie.d2_xml;import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import javax.xml.parsers.SAXParser;
import java.util.List;/*
* 目标:掌握Dom4J框架解析XML文件
* */
public class Dom4JTest1 {public static void main(String[] args) throws Exception {//1、创建一个Dom4J框架提供的解析器对象SAXReader saxReader = new SAXReader();//2、使用saxReader对象把需要解析的XML文件都城一个Document对象。Document document =saxReader.read("properties-xml-log-app\\src\\holleoworld.xml");//3、从文档对象中解析XML文件的全部数据Element root = document.getRootElement();System.out.println(root.getName());//4、获取根元素下的全部一级子元素//List<Element> elements = root.elements();List<Element> elements = root.elements("user");for (Element element : elements) {System.out.println(element.getName());}//5、获取当前元素下的某个子元素Element people = root.element("people");System.out.println(people.getText());//如果下面有很多子元素user,默认获取第一个Element user = root.element("user");System.out.println(user.elementText("name"));//6、获取元素的属性信息System.out.println(user.attributeValue("id"));Attribute id = user.attribute("id");System.out.println(id.getName());System.out.println(id.getValue());List<Attribute> attributes = user.attributes();for (Attribute attribute : attributes) {System.out.println(attribute.getName() + "=" + attribute.getValue());}//7、如何获取全部的文本内容:获取当前元素下的子元素文本值System.out.println(user.elementText("name"));System.out.println(user.elementText("地址"));System.out.println(user.elementTextTrim("地址"));//取出文本去除前后空格System.out.println(user.elementText("password"));Element data = user.element("data");System.out.println(data.getText());System.out.println(data.getTextTrim());//取出文本去除前后空格}
}
DOM4)解析XML文件的思想:文档对象模型

Dom4j解析XML-得到Document对象

SAXReader:Dom4j提供的解析器,可以认为是代表整个Dom4j框架

Document

Element提供的方法

使用程序把数据写出到文件中去

如可使用程序把数据写出到XML文件中去?不建议用dom4j故推荐直接把程序里的数据拼接成XML格式,然后用IO流写出去!

package com.itchinajie.d2_xml;import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.io.*;
import java.util.List;/*
* 目标:如何使用程序把数据写入到XML文件中去。
* <book><name>从入门到跑路</name><author>dlei</author><price>999.9</price>
</b00k>
* */
public class Dom4JTest2 {public static void main(String[] args) throws Exception {//1、使用一个StringBuilder对象来拼接XML格式的数据StringBuilder sb = new StringBuilder();sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\r\n");sb.append("<book>\r\n");sb.append("\t<name>").append("从入门到跑路").append("</name>\r\n");sb.append("\t<author>").append("dlei").append("</author>\r\n");sb.append("\t<price>").append("999.9").append("</price>\r\n");sb.append("</book>");try (BufferedWriter bw = new BufferedWriter(new FileWriter("properties-xml-log-app\\src\\book.xml"));){bw.write(sb.toString());} catch (Exception e) {e.printStackTrace();}}
}

补充知识:约束XML文件的编写(了解)

约束XML文件的书写

限制XML文件只能按照某种格式进行书写。

约束文档

专门用来限制xml书写格式的文档,比如:限制标签、属性应该怎么写。

约束文档的分类

DTD文档

Schema文档

日志技术

日志

希望系统能记住某些数据是被谁操作的,比如被谁删除了?

想分析用户浏览系统的具体情况,以便挖掘用户的具体喜好?

当系统在开发中或者上线后出现了bug,崩溃了,该通过什么去分析、定位bug?

日志就好比生活中的日记,可以记录你生活中的点点滴滴。

程序中的日志,通常就是一个文件,里面记录的是程序运行过程中的各种信息

目前记录日志的方案

输出语句的弊端

日志会展示在控制台

不能更方便的将日志记录到其他的位置(文件,数据库)

想取消日志,需要修改源代码才可以完成

而日志技术可以将系统执行的信息,方便的记录到指定的位置(控制台、文件中、数据库中)。

可以随时以开关的形式控制日志的启停,无需侵入到源代码中去进行修改。

日志技术的体系、Logback日志框架的概述

日志技术的体系结构

日志框架:牛人或者第三方公司已经做好的实现代码,后来者直接可以拿去使用。
日志接口:设计日志框架的一套标准,日志框架需要实现这些接口。
注意1:因为对Commons Logging接口不满意,有人就搞了SLF4J;因为对Log4j的性能不满意,有人就搞了Logback。
注意2:Logback是基于slf4j的日志规范实现的框架。

Logback日志框架官方网站:https://logback.qos.ch/index.html

Logback日志框架有以下几个模块:

想使用Logback日志框架,至少需要在项目中整合如下三个模块:

Logback快速入门

package com.itchinajie.d3_log;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*
* 目标:掌握LogBack日志框架的使用
* */
public class LogBackTest {//创建一个Logger日志对象public static final Logger LOGGER = LoggerFactory.getLogger("LogBackTest");public static void main(String[] args) {try {LOGGER.info("chu方法开始执行~~");chu(10,0);LOGGER.info("chu方法执行成功~~");} catch (Exception e) {LOGGER.error("chu方法执行失败了,出现了bug~~~");}}public static void chu(int a,int b){LOGGER.debug("参数a:" + a);LOGGER.debug("参数b:" + b);int c = a / b;LOGGER.info("结果是:" + c);}
}

LogBack设置日志级别

日志级别

日志级别指的是日志信息的类型,日志都会分级别,常见的日志级别如下(优先级依次升高

为什么要学习日志级别?

只有日志的级别是大于或等于核心配置文件配置的日志级别,才会被记录,否则不记录。

多线程

线程和多线程

线程

线程(Thread)是一个程序内部的一条执行流程。

程序中如果只有一条执行流程,那这个程序就是单线程的程序。

多线程

多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行),消息通信、淘宝、京东系统都离不开多线程技术。

多线程的创建

线程创建方式一:继承Thread类

如何在程序中创建出多条线程?

Java是通过java.lang.Thread类的对象来代表线程的。

继承Thread类步骤

1、定义一个子类MyThread:继承线程类java.lang.Thread,重写run()方法

2、创建MyThread类的对象

3、调用线程对象的start()方法启动线程(启动后还是执行run方法的)

package com.itchinajie.d1_create_thread;
/*
*目标:掌握线程的创建方式一:继承Thread类
* */
public class ThreadTest1 {//main方法是由一条默认的主线程负责执行public static void main(String[] args) {//3、创建MyThread线程类的对象代表一个线程Thread t = new MyThread();//4、启动线程(自动执run方法的)t.start();for (int i = 1; i <= 5 ; i++) {System.out.println("主线程main输出:" + i);}}
}package com.itchinajie.d1_create_thread;
/*
* 1、让子类继承Thread线程类
* */
public class MyThread extends Thread{@Overridepublic void run() {for (int i = 1; i <= 5 ; i++) {System.out.println("子线程MyThread输出:" + i);}}
}

方式一优缺点:

优点:编码简单

缺点:线程类已经继承Thread,无法继承其他类,不利于功能的扩展。

多线程的注意事项

1、启动线程必须是调用start方法,不是调用run方法。

直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。

只有调用start方法才是启动一个新的线程执行。

2、不要把主线程任务放在启动子线程之前。

这样主线程一直是先跑完的,相当于是一个单线程的效果了。

线程创建方式二:实现Runnable接口

实现Runnable接口

1、定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法

2、创建MyRunnable任务对象

3、把MyRunnable任务对象交给Thread处理。

1、调用线程对象的start()方法启动线程

package com.itchinajie.d1_create_thread;
/*
*目标:掌握线程的创建方式二:实现Runnable接口
* */
public class ThreadTest2 {//main方法是由一条默认的主线程负责执行public static void main(String[] args) {//3、创建任务对象Runnable target = new MyRunnable();//4、把任务对象交给一个线程对象来处理// public Thread(Runnable task)new Thread(target).start();for (int i = 1; i <= 5 ; i++) {System.out.println("主线程main输出:" + i);}}
}package com.itchinajie.d1_create_thread;
/*
* 1、定义一个任务类,实现Runnable接口
* */
public class MyRunnable implements Runnable{//2、重写runnable的run方法@Overridepublic void run() {//线程要执行的方法for (int i = 1; i <= 5 ; i++) {System.out.println("子线程输出:" + i);}}
}

方式二的优缺点

优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。

缺点:需要多一个Runnable对象。

线程创建方式二的匿名内部类写法

1、可以创建Runnable的匿名内部类对象。

2、再交给Thread线程对象。

3、再调用线程对象的start()启动线程。

package com.itchinajie.d1_create_thread;
/*
*目标:掌握线程的创建方式二:实现Runnable接口,方式二
* */
public class ThreadTest2_2 {public static void main(String[] args) {//1、直接创建Runnable接口的匿名内部类形式(任务对象)Runnable target = new Runnable() {@Overridepublic void run() {for (int i = 1; i <= 5 ; i++) {System.out.println("子线程1输出:" + i);}}};new Thread(target).start();//简化接形式1:使用匿名内部类new Thread(new Runnable() {@Overridepublic void run() {for (int i = 1; i <= 5 ; i++) {System.out.println("子线程2输出:" + i);}}}).start();//简化接形式2:再加个Lambda表达式new Thread(() -> {for (int i = 1; i <= 5 ; i++) {System.out.println("子线程2输出:" + i);}}).start();for (int i = 1; i <= 5 ; i++) {System.out.println("主线程main输出:" + i);}}
}

线程创建方式三:实现Callable接口

前两种线程创建方式存在的一个问题

假如线程执行完毕后有一些数据需要返回,他们重写的run方法均不能直接返回结果。

怎么解中这个问题?

JDK5.0提供了Callable接口和FutureTask类来实现(多线程的第三种创建方式)。

这种方式最大的优点:可以返回线程执行完毕后的结果。

利用Callable接口、FutureTask类来实现。
1、创建任务对象

定义一个类实现Callable接口,重写call方法,封装要做的事情,和要返回的数据。

把Callable类型的对象封装成FutureTask(线程任务对象)。

2、把线程任务对象交给Thread对象。

3、调用Thread对象的start方法启动线程。

4、线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。

FutureTask的API

package com.itchinajie.d1_create_thread;import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*
* 目标:掌握线程的创建方式三:实现Callable接口
* */
public class ThreadTest3 {public static void main(String[] args) throws Exception {//3、创建一个Callable对象Callable<String> call = new MyCallable(100);//4、把Callable的对象封装成一个FutureTask对象(任务对象)//未来任务对象的作用?//1、是一个任务对象,实现了Runnable对象//2、可以在线程执行完毕之后,用未来任务对象调用get方法获取线程执行完毕后FutureTask<String> f1 = new FutureTask<>(call);//5、把任务对象交给一个Thread对象new Thread(f1).start();Callable<String> call2 = new MyCallable(200);FutureTask<String> f2 = new FutureTask<>(call2);new Thread(f2).start();//6、获取线程执行完毕后返回的结果。//注意:如果执行到这儿,假如上面的线程还没有执行完毕//这里的代码会暂停,等待上面线程执行完毕后才会获取结果String rs = f1.get();System.out.println(rs);String rs2 = f2.get();System.out.println(rs2);}
}package com.itchinajie.d1_create_thread;import java.util.concurrent.Callable;
/*
* 1、让这个类实现Callable接口
* */
public class MyCallable implements Callable<String> {private int n;public MyCallable(int n) {this.n = n;}//2、重写call方法@Overridepublic String call() throws Exception {//描述线程的任务,返回线程执行返回后的结果//需求:求1-n的和返回。int sum = 0;for (int i = 1; i <= n ; i++) {sum += i;}return "线程求出了1-" + n + "的和是:" + sum;}
}

线程创建方式三的优缺点

优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强;可以在线程执行完毕后去获取线程执行的结果。

缺点:编码复杂一点。

Thread的常用方法

 Thread提供了很多与多线程操作的相关方法

package com.itchinajie.d2_thread_api;public class MyThread extends Thread{public MyThread(String name){super(name);//为当前线程设置名字}@Overridepublic void run() {//哪个线程执行它,他就会得到哪个线程对象Thread t = Thread.currentThread();for (int i = 1; i <= 3 ; i++) {System.out.println(t.getName() + "输出:" + i);}}
}package com.itchinajie.d2_thread_api;
/*
* 目标:掌握Thread的常用方法
* */
public class ThreadTest1 {public static void main(String[] args) {Thread t1 = new MyThread("1号线程");//t1.setName("1号线程");t1.start();System.out.println(t1.getName());//Thread-0Thread t2 = new MyThread("2号线程");//t2.setName("2号线程");t2.start();System.out.println(t2.getName());//Thread-1//主线程对象的名字//那个线程执行它,他就会得到哪个线程的对象Thread m = Thread.currentThread();//m.setName("最牛的线程");System.out.println(m.getName());//mainfor (int i = 0; i <=5 ; i++) {System.out.println(m.getName() + "线程输出:" + i);}}
}package com.itchinajie.d2_thread_api;
/*
* 目标:掌握sleep方法,join方法的作用
* */
public class ThreadTest2 {public static void main(String[] args) throws Exception {for (int i = 1; i <=5 ; i++) {System.out.println(i);System.out.println(i);//休眠5sif(i == 3){//会让当前执行的线程暂停5s,再继续进行//项目经理让我加上这行代码,如果用户交钱了,我就注释掉!Thread.sleep(5000);}}//join方法作用:当前调用这个方法的线程先执行完Thread t1 = new MyThread("1号线程");t1.start();t1.join();Thread t2 = new MyThread("2号线程");t2.start();t2.join();Thread t3 = new MyThread("3号线程");t3.start();t3.join();}
}

线程安全

线程安全问题

什么是线程安全问题?

多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。

取钱的线程安全问题

场景:小明和小红是一对夫妻,他们有一个共同的账户,余额是10万元,如果小明和
小红同时来取钱,并且2人各自都在取钱10万元,可能会出现什么问题呢?

线程安全问题出现的原因

1、存在多个线程在同时执行

2、同时访问一个共享资源

3、存在修改该共享资源

用程序模拟线程安全问题

创建Account类

package com.itchinajie.d3_thread_safe;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class Account {private String cardId;private double money;//创建了一个锁对象private final Lock lk = new ReentrantLock();public static void test(){synchronized (Account.class){}}//小明,小红同时过来的public  void drawMoney(double money){//先搞清楚是谁来取钱String name = Thread.currentThread().getName();try {lk.lock();//加锁//1、判断月是否足够if (this.money >= money){System.out.println(name + "来取钱" + money + "成功!");this.money -= money;System.out.println(name + "来取钱后,余额剩余:" + this.money);}else {System.out.println(name + "来取钱,余额不足~");}} finally {lk.unlock();//解锁}}public String getCardId() {return cardId;}public void setCardId(String cardId) {this.cardId = cardId;}public Double getMoney() {return money;}public void setMoney(double money) {this.money = money;}public Account() {}public Account(String cardId, double money) {this.cardId = cardId;this.money = money;}
}

创建DrawThread类

package com.itchinajie.d3_thread_safe;public class DrawThread extends Thread{private Account acc;public DrawThread(Account acc,String name){super(name);this.acc = acc;}@Overridepublic void run() {//取钱(小红 小明)acc.drawMoney(100000);}
}

创建ThreadTest类

package com.itchinajie.d3_thread_safe;
/*
* 目标:模拟线程安全问题
* */
public class ThreadTest {public static void main(String[] args) {//1、创建一个账户对象,代表两个人的共享账户Account acc = new Account("123456",100000);//2、创建两个线程,分别代表小明和小红,再去同一个账户对象中取10万new DrawThread(acc,"小明").start();//小明new DrawThread(acc,"小红").start();//小红Account acc1 = new Account("1234567",100000);//2、创建两个线程,分别代表小明和小红,再去同一个账户对象中取10万new DrawThread(acc1,"小黑").start();//小黑new DrawThread(acc1,"小白").start();//小白}
}

线程同步

线程同步

线程同步解决线程安全问题的方案。

线程同步的思想

让多个线程实现先后依次访问共享资源,这样就解决了安全问题。

线程同步的常见方案

加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。

方式一:同步代码块

同步代码块

作用:把访问共享资源的核心代码给上锁,以此保证线程安全。

原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。

同步锁的注意事项

对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。

锁对象随便选择一个唯一的对象好不好呢?

不好,会影响其他无关线程的执行。

锁对象的使用规范

建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象。

对于静态方法建议使用字节码(类名.class)对象作为锁对象。

方式二:同步方法

同步方法

作用:把访问共享资源的核心方法给上锁,以此保证线程安全。

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

同步方法底层原理

同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。

如果方法是实例方法:同步方法默认用this作为的锁对象。

如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

1、是同步代码块好还是同步方法好一点?

范围上:同步代码块锁的范围更小,同步方法锁的范围更大。

可读性:同步方法更好

方式三:Lock锁

Lock锁

Lock锁是DK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。

Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。

Lock的常用方法

线程通信

什么是线程通信?

当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。

线程通信的常见模型(生产者与消费者模型)

生产者线程负责生产数据

消费者线程负责消费生产者生产的数据。

注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产!

Object类的等待和唤醒方法:

注意:上述方法应该使用当前同步锁对象进行调用。

创建Desk类

package com.itchinajie.d4_thread_communication;import java.util.ArrayList;
import java.util.List;public class Dask {private List<String> list = new ArrayList<>();public synchronized void put(){try {String name = Thread.currentThread().getName();//判断是否有包子if(list.size() == 0){list.add(name + "做的肉包子");System.out.println(name + "做了一个肉包子~~");Thread.sleep(2000);//唤醒别人,等待自己this.notifyAll();this.wait();}} catch (Exception e) {e.printStackTrace();}}public synchronized void get(){try {String name = Thread.currentThread().getName();if (list.size() == 1) {//有包子,吃了System.out.println(name + "吃了:" + list.get(0));list.clear();Thread.sleep(1000);this.notifyAll();this.wait();}else {//没有包子this.notifyAll();this.wait();}} catch (Exception e) {e.printStackTrace();}}}

创建ThreadTest类

package com.itchinajie.d4_thread_communication;
/*
* 目标:了解线程通信
* */
public class ThreadTest {public static void main(String[] args) {//需求:3个生产者线程,负责生产包子,每个线程每次只能生产1个包子放在桌子//2个消费者线程负责吃包子,每人每次只能从桌子上拿1个包子吃。Dask dask = new Dask();//创建3个生产者线程(3个厨师)new Thread(() -> {while(true){dask.put();}},"厨师1").start();new Thread(() -> {while(true){dask.put();}},"厨师2").start();new Thread(() -> {while(true){dask.put();}},"厨师3").start();new Thread(() -> {while(true){dask.get();}},"吃货1").start();new Thread(() -> {while(true){dask.get();}},"吃货2").start();}
}

线程池

什么是线程池?

线程池就是一个可以复用线程的技术。

不使用线程池的问题

用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,而创建新线程的开销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。

线程池的工作原理

创建MyCallable类实现Callable接口

package com.itchinajie.d4_thread_communication;import java.util.ArrayList;
import java.util.List;public class Dask {private List<String> list = new ArrayList<>();public synchronized void put(){try {String name = Thread.currentThread().getName();//判断是否有包子if(list.size() == 0){list.add(name + "做的肉包子");System.out.println(name + "做了一个肉包子~~");Thread.sleep(2000);//唤醒别人,等待自己this.notifyAll();this.wait();}} catch (Exception e) {e.printStackTrace();}}public synchronized void get(){try {String name = Thread.currentThread().getName();if (list.size() == 1) {//有包子,吃了System.out.println(name + "吃了:" + list.get(0));list.clear();Thread.sleep(1000);this.notifyAll();this.wait();}else {//没有包子this.notifyAll();this.wait();}} catch (Exception e) {e.printStackTrace();}}}

创建MyRunnable类继承Runnable接口

package com.itchinajie.d5_thread_pool;public class MyRunnable implements Runnable{@Overridepublic void run() {//任务是干啥的?System.out.println(Thread.currentThread().getName() + "==> 输出666~~");try {Thread.sleep(Integer.MAX_VALUE);} catch (InterruptedException e) {e.printStackTrace();}}
}

如何创建线程池

谁代表线程池?

JDK5.0起提供了代表线程池的接口:ExecutorService.

如何得到线程池对象?

方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象。

​
package com.itchinajie.d5_thread_pool;import java.sql.Time;
import java.util.concurrent.*;/*
* 目标:掌握线程池的创建
* */
public class ThreadPoolTest1 {public static void main(String[] args) {//1、通过ThreadPoolExecutor创建一个线程池对象/*public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {*/ExecutorService pool = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,new ArrayBlockingQueue<>(4),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());}
}​

方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。

package com.itchinajie.d5_thread_pool;import java.util.concurrent.*;/*
* 目标:掌握线程池的创建
* */
public class ThreadPoolTest2 {public static void main(String[] args) {//1、通过ThreadPoolExecutor创建一个线程池对象ExecutorService pool = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,new ArrayBlockingQueue<>(4),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());Runnable target = new MyRunnable();pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!pool.execute(target);//线程池会自动创建一个新线程,自动处理这个任务,自动执行的!pool.execute(target);pool.execute(target);pool.execute(target);pool.execute(target);//到了临时线程的创建时机了pool.execute(target);pool.execute(target);//到了新任务拒绝的时机了pool.execute(target);//pool.shutdown();//等着线程池的任务全部执行完毕后,再关闭线程池//pool.shutdownNow();//立即关闭线程池!不管任务是否执行完毕!}
}
线程池的注意事项

1、临时线程什么时候创建?

新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

2、什么时候会开始拒绝新任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。

package com.itchinajie.d5_thread_pool;import java.util.concurrent.*;/*
* 目标:掌握线程池的创建
* */
public class ThreadPoolTest3 {public static void main(String[] args) throws ExecutionException, InterruptedException {//1、通过ThreadPoolExecutor创建一个线程池对象ExecutorService pool = new ThreadPoolExecutor(3,5,8,TimeUnit.SECONDS,new ArrayBlockingQueue<>(4),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());//2、使用线程处理Callable任务Future<String> f1 = pool.submit(new MyCallable(100));Future<String> f2 = pool.submit(new MyCallable(200));Future<String> f3 = pool.submit(new MyCallable(300));Future<String> f4 = pool.submit(new MyCallable(400));System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());}
}
package com.itchinajie.d5_thread_pool;import java.util.concurrent.*;/*
* 目标:掌握线程池的创建
* */
public class ThreadPoolTest4 {public static void main(String[] args) throws ExecutionException, InterruptedException {//1、通过ThreadPoolExecutor创建一个线程池对象
//        ExecutorService pool = new ThreadPoolExecutor(3,5,8,
//                TimeUnit.SECONDS,new ArrayBlockingQueue<>(4),Executors.defaultThreadFactory()
//        ,new ThreadPoolExecutor.AbortPolicy());//1-2通过Executors创建一个线程池任务ExecutorService pool = Executors.newCachedThreadPool();//核心线程数量到底配置多少呢//计算密集型任务:核心线程数量 = CPU的核心数 + 1//IO密集型的任务:核心线程数量 = CPU核数 * 2//2、使用线程处理Callable任务Future<String> f1 = pool.submit(new MyCallable(100));Future<String> f2 = pool.submit(new MyCallable(200));Future<String> f3 = pool.submit(new MyCallable(300));Future<String> f4 = pool.submit(new MyCallable(400));System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());}
}

线程池处理Runnable任务

ExecutorService的常用方法

新任务拒绝策略

线程池处理Callable任务

ExecutorService的常用方法

Executors工具类实现线程池

Executors

Executors是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。

注意:这些方法的底层,都是通过线程池的实现类ThreadPoolExecutort创建的线程池对象。

Executors使用可能存在的陷阱

大型并发系统环境中使用Executors如果不注意可能会出现系统风险。

其它细节知识:并发、并行

进程

正在运行的程序(软件)就是一个独立的进程。

线程是属于进程的,一个进程中可以同时运行很多个线程。

进程中的多个线程其实是并发和并行执行的。

并发

进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。

并行

在同一个时刻上,同时有多个线程在被CPU调度执行。

多线程是怎么执行的?

并发并行同时执行

其它细节知识:线程的生命周期

线程的生命周期也就是线程从生到死的过程中,经历的各种状态及状态转换。

理解线程这些状态有利于提升并发编程的理解能力。

Java线程的状态

Java总共定义了6种状态,6种状态都定义在Thread类的内部枚举类中。

线程6种状态的互相转换

六种状态总结

乐观锁、多线程练习题

创建MyRunnable1类

package com.itchinajie.d6_tz;public class MyRunnable1 implements Runnable{private int count;@Overridepublic void run() {synchronized (this) {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() +"=======>" + (++count));}}}
}

创建MyRunnable2类

package com.itchinajie.d6_tz;import java.util.concurrent.atomic.AtomicInteger;public class MyRunnable2 implements Runnable{private AtomicInteger count = new AtomicInteger();//整数修改的乐观锁:原子类实现的@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() +"=======>" + count.incrementAndGet());}}
}

Test1

package com.itchinajie.d6_tz;public class Test1 {public static void main(String[] args) {//目标:拓展悲观锁,乐观锁//悲观锁:一上来就加锁,没有安全感。每次只能一个线程进入访问完毕后,再解锁。线程安全,性能较差!//乐观锁:一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制。线程安全,性能较好。//需求:1个静态变量,100个线程,每个线程对其加100次。Runnable target = new MyRunnable1();for (int i = 1; i <=100 ; i++) {new Thread(target).start();}}}

 Test2

package com.itchinajie.d6_tz;public class Test2 {public static void main(String[] args) {//目标:拓展悲观锁,乐观锁//悲观锁:一上来就加锁,没有安全感。每次只能一个线程进入访问完毕后,再解锁。线程安全,性能较差!//乐观锁:一开始不上锁,认为是没有问题的,大家一起跑,等要出现线程安全问题的时候才开始控制。线程安全,性能较好。//需求:1个静态变量,100个线程,每个线程对其加100次。Runnable target = new MyRunnable2();for (int i = 1; i <=100 ; i++) {new Thread(target).start();}}}

 实例练习

Demo

package com.itchinajie.d7_Test;import java.util.ArrayList;
import java.util.List;
import java.util.Random;public class Demo1 {public static void main(String[] args) throws Exception {//目标:有100份礼品,小红,小明两人同时发送,当剩下的礼品小于10份的时候则不再送出,//利用多线程模拟该过程并将线程的名称打印出来。并最后在控制台分别打印小红,小明各自送出多少分礼物。//1、拿100份礼品到程序中来List<String> gift = new ArrayList<>();Random r = new Random();String[] names = {"包包","口红","鲜花","剃须刀","皮带","手表"};for (int i = 0; i < 100; i++) {gift.add(names[r.nextInt(names.length)] + (i+1));}System.out.println(gift);//2、定义线程类、创建线程对象、去集合中拿礼物给别人SendThread xm = new SendThread(gift,"小明");xm.start();SendThread xh = new SendThread(gift,"小红");xh.start();xm.join();xh.join();System.out.println(xm.getCount());System.out.println(xh.getCount());}
}

 SendThread

package com.itchinajie.d7_Test;import java.util.List;
import java.util.Random;public class SendThread extends Thread{private List<String> gift ;private int count;public SendThread(List<String> gift, String name) {super(name);this.gift = gift;}public int getCount() {return count;}public void setCount(int count) {this.count = count;}@Overridepublic void run() {String name = Thread.currentThread().getName();//小明、小红发礼物出去//实现线程安全问题Random r = new Random();while (true) {synchronized (gift){//注意:锁必须唯一if (gift.size() < 10) {break;}String rs = gift.remove(r.nextInt(gift.size()));System.out.println(name + "发出了:" + rs);count++;}}}
}

网络通信

网络编程

可以让设备中的程序与网络上其他设备中的程序进行数据交互(实现网络通信的)。

基本的通讯架构

基本的通信架构有2种形式:CS架构(Client客户端/Server服务端)、BS架构(Browser浏览器/Server服务端)。

无论是CS架构,还是BS架构的软件都必须依赖网络编程!

网络通信三要素:IP地址、端口号、 协tong

网络通信的关键三要素

IP地址

IP(Internet Protocol):全称”互联网协议地址”,是分配给上网设备的唯一标志。

IP地址有两种形式:IPV4、IPV6

IPV4

IPV6地址

IPV6:共128位,号称可以为地球每一粒沙子编号。
PV6分成8段表示,每段每四位编码成一个十六进制位表示,数之间用冒号(:)分开。

公网IP,内网IP

公网IP:是可以连接互联网的P地址;内网IP:也叫局域网IP,只能组织机构内部使用。

192.168.开头的就是常见的局域网地址,范围即为192.168.0.0--192.168.255.255,专门为组织机构内部使用。

特殊P地址:

127.0.0.1、localhost:代表本机IP,只会寻找当前所在的主机。

IP常用命令:

ipconfig:查看本机IP地址。

ping IP:地址:检查网络是否连通。

InetAddress

InetAddress代表IP地址。

InetAddress的常用方法如下

package com.itchinajie.d1_ip;import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;/*
* //目标:掌握InetAddress类的使用
* */
public class InetAddressTest {public static void main(String[] args) throws Exception {//1、获取本机IP地址对象的InetAddress ip1 = InetAddress.getLocalHost();System.out.println(ip1.getHostName());System.out.println(Arrays.toString(ip1.getAddress()));//2、获取指定IP或者域名的IP地址对象InetAddress ip2 = InetAddress.getByName("www.baidu.com");System.out.println(ip2.getHostName());System.out.println(ip2.getAddress());//ping www.baidu.comSystem.out.println(ip2.isReachable(6000));}
}

端口号

端口

标记正在计算机设备上运行的应用程序的,被规定为一个16位的二进制,范围是0~65535.

分类

周知端口:0~1023,被预先定义的知名应用占用(如:HTTP占用80,FTP占用21)

注册端口:1024~49151,分配给用户进程或某些应用程序。

动态端口:49152到65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配。

注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错。

协议

开放式网络互联标准:OSI引网络参考模型

OS引网络参考模型:全球网络互联标准。

TCP/IP网络模型:事实上的国际标准。

通信协议

网络上通信的设备,事先规定的连接规则,以及传输数据的规则被称为网络通信协议。

为了让全球所有的上网设备都能够互联

传输层的2个通信协议

UDP(User Datagram Protocol)):用户数据报协议;TCP(Transmission Control Protocol):传输控制协议。

UDP协议

特点:无连接、不可靠通信。通言数率高!语音通话、视频直播

不事先建立连接,数据按照包发,一包数据包含:自己的IP、程序端口,目的地IP、程序端口和数据(限制在64KB内)等。

发送方不管对方是否在线,数据在中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的。

TCP协议

特点:面向连接、可靠通信。通官数率相对不高!网页、文件下载、支付。

TCP的最终目的:要保证在不可靠的信道上实现可靠的传输。

TCP主要有三个步骤实现可靠传输:三次握手建立连接,传输数据进行确认,四次挥手断开连接。

TCP协议:三次握手建立可靠连接

传输数据会进行确认,以保证数据传输的可靠性

TCP协议:四次握手断开连接

目的:确保双方数据的收发都已经完成!

UDP通信-快速入门

UDP通信

特点:无连接、不可靠通信。

不事先建立连接;发送端每次把要发送的数据(限制在64KB内)、接收端1P、等信息封装成一个数据包,发出去就不管了。

Java提供了一个java.net.DatagramSocket类来实现UDP通信。

DatagramSocket:用于创建客户端、服务端

DatagramPacket:创建数据包

创建Client类

package com.itchinajie.d2_upd1;import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;/*
* 目标:完成UDP通信快速入门:实现1发1收
* */
public class Client {public static void main(String[] args) throws Exception {//1、创建客户端对象(发韭菜出去的人)DatagramSocket socket = new DatagramSocket(7777);//2、创建数据包对象封装要发出去的数据(创建一个韭菜盘子)/*public DatagramPacket(byte[] buf, int offset, int length,InetAddress address, int port) {参数一:封装要发出去的数据参数二:发送出去的数据大小(字节个数)参数三:服务端的IP地址(找到服务端主机)参数四:服务端程序的端口*/byte[] bytes = "我是举哀了的客户端,我爱你abc".getBytes();DatagramPacket packet = new DatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),6666);//3、开始正式发送这个数据报的数据出去了socket.send(packet);System.out.println("客户端数据发送完毕~~~");socket.close();//释放资源}
}

 创建Server类

package com.itchinajie.d2_upd1;import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;/*
* 目标:完成DUP通信快速入门:服务端开发
* */
public class Server {public static void main(String[] args) throws Exception {System.out.println("---服务端已启动---");//1、创建一个服务器对象(创建一个接韭菜的人)  注册端口DatagramSocket socket = new DatagramSocket(6666);//2、创建一个数据包对象,用于接收数据的(常见一个就韭菜盘子)byte[] buffer = new byte[1024 * 64];//64KBDatagramPacket packet = new DatagramPacket(buffer, buffer.length);//3、开始正式使用数据包来接收客户端发来的数据socket.receive(packet);//4、从字节数组中,把该收到的数据直接打印出来//接收了多少就倒出多少//获取本次数据包接收了多少数据int len = packet.getLength();String rs = new String(buffer,0,len);System.out.println(rs);System.out.println(packet.getAddress().getHostAddress());System.out.println(packet.getPort());socket.close();//释放资源}
}

UDP通信-多发多收

Client

package com.itchinajie.d3_upd2;import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;/*
* 目标:完成UDP通信快速入门:实现多发多收
* */
public class Client {public static void main(String[] args) throws Exception {//1、创建客户端对象(发韭菜出去的人)DatagramSocket socket = new DatagramSocket(9898);//2、创建数据包对象封装要发出去的数据(创建一个韭菜盘子)/*public DatagramPacket(byte[] buf, int offset, int length,InetAddress address, int port) {参数一:封装要发出去的数据参数二:发送出去的数据大小(字节个数)参数三:服务端的IP地址(找到服务端主机)参数四:服务端程序的端口*/Scanner sc = new Scanner(System.in);while (true) {System.out.println("请说:");String msg = sc.nextLine();//一旦发现用户输入的exit命令,就退出客户端if ("exit".equals(msg)) {System.out.println("欢迎下次光临!!");socket.close();//释放资源break;//跳出死循环}byte[] bytes = msg.getBytes();DatagramPacket packet = new DatagramPacket(bytes,bytes.length,InetAddress.getLocalHost(),6666);//3、开始正式发送这个数据报的数据出去了socket.send(packet);}}
}

Server

package com.itchinajie.d3_upd2;import java.net.DatagramPacket;
import java.net.DatagramSocket;/*
* 目标:完成DUP通信快速入门:服务端开发
* */
public class Server {public static void main(String[] args) throws Exception {System.out.println("---服务端已启动---");//1、创建一个服务器对象(创建一个接韭菜的人)  注册端口DatagramSocket socket = new DatagramSocket(6666);//2、创建一个数据包对象,用于接收数据的(常见一个就韭菜盘子)byte[] buffer = new byte[1024 * 64];//64KBDatagramPacket packet = new DatagramPacket(buffer, buffer.length);while (true) {//3、开始正式使用数据包来接收客户端发来的数据socket.receive(packet);//4、从字节数组中,把该收到的数据直接打印出来//接收了多少就倒出多少//获取本次数据包接收了多少数据int len = packet.getLength();String rs = new String(buffer,0,len);System.out.println(rs);System.out.println(packet.getAddress().getHostAddress());System.out.println(packet.getPort());System.out.println("---------------------------------------------");//            socket.close();//不用关}}
}

TCP通信-快速入门

TCP通信

特点:面向连接、可靠通信。

通信双方事先会采用“三次握手”方式建立可靠连接,实现端到端的通信;底层能保证数据成功传给服务端。

Java提供了一个java.net.Socket类来实现TCP通信。

TCP通信之-客户端开发

客户端程序就是通过java.net包下的Socket类来实现的。

TCP通信-服务端程序的开发

服务端是通过java.net包下的ServerSocket:类来实现的。

ServerSocket

创建Client类

package com.itchinajie.d4_tcp1;import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;/*
* 目标:完成TCP通信快速入门-客户端开发:实现1发1收
* */
public class Client {public static void main(String[] args) throws Exception {//1、创建Socket对象,并同时请求与服务端程序的连接Socket socket = new Socket("127.0.0.1",8888);//2、从Socket通信管道中得到一个字节输出流,用来发数据给服务端程序。OutputStream os = socket.getOutputStream();//3、把低级的字节输出流包装成数据输出流DataOutputStream dos = new DataOutputStream(os);//4、开始写数据出去了dos.writeUTF("在一起,好吗?");dos.close();socket.close();//释放连接资源}
}

创建Server类 

package com.itchinajie.d4_tcp1;import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
* 目标:完成TCP通信快速入门-服务端开发:实现1发1收。
* */
public class Server {public static void main(String[] args) throws Exception {System.out.println("---服务端启动成功----");//1、创建ServerSocket的对象,同时为服务端注册端口。ServerSocket serverSocket =  new ServerSocket(8888);//2、使用serverSocket.对象,调用一个accept方法,等待客户端的连接请求Socket socket = serverSocket.accept();//3、从socket通信管道中得到一个字节输入流。InputStream is = socket.getInputStream();//4、把原始的字节输入流包装成数据输入流DataInputStream dis = new DataInputStream(is);//5、使用数据输入流读取客户端发送过来的消息String rs = dis.readUTF();System.out.println(rs);//其实我们也可以获取客户端的IP地址System.out.println(socket.getRemoteSocketAddress());dis.close();socket.close();}
}

这样就实现了客户端发一消息,服务端接收,先启动Server类,再启动Client类。

TCP通信-多发多收

相较上面的一发一收,我们只需要把写入数据的步骤进入死循环就行。

Client

package com.itchinajie.d5_tcp2;import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;/*
* 目标:完成TCP通信快速入门-客户端开发:实现1发1收
* */
public class Client {public static void main(String[] args) throws Exception {//1、创建Socket对象,并同时请求与服务端程序的连接Socket socket = new Socket("127.0.0.1",8888);//2、从Socket通信管道中得到一个字节输出流,用来发数据给服务端程序。OutputStream os = socket.getOutputStream();//3、把低级的字节输出流包装成数据输出流DataOutputStream dos = new DataOutputStream(os);Scanner sc = new Scanner(System.in);while (true) {System.out.println("请说:");String msg = sc.nextLine();//一旦发现用户输入的exit命令,就退出客户端if ("exit".equals(msg)) {System.out.println("欢迎下次光临!!");dos.close();socket.close();//释放资源break;//跳出死循环}//4、开始写数据出去了dos.writeUTF(msg);dos.flush();}}
}

Server

package com.itchinajie.d5_tcp2;import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
* 目标:完成TCP通信快速入门-服务端开发:实现1发1收。
* */
public class Server {public static void main(String[] args) throws Exception {System.out.println("---服务端启动成功----");//1、创建ServerSocket的对象,同时为服务端注册端口。ServerSocket serverSocket =  new ServerSocket(8888);//2、使用serverSocket.对象,调用一个accept方法,等待客户端的连接请求Socket socket = serverSocket.accept();//3、从socket通信管道中得到一个字节输入流。InputStream is = socket.getInputStream();//4、把原始的字节输入流包装成数据输入流DataInputStream dis = new DataInputStream(is);while (true) {try {//5、使用数据输入流读取客户端发送过来的消息String rs = dis.readUTF();System.out.println(rs);} catch (Exception e) {System.out.println(socket.getRemoteSocketAddress() + "离线了!");dis.close();socket.close();break;}}}
}

TCP通信-支持与多个客户端同时通信

目前我们开发的服务端程序,不可以支持与多个客户端同时通信,因为服务端现在只有一个主线程,只能处理一个客户端的消息。

把客户端对应的socket通信管道,交给一个独立的线程负责处理。

Client类同上

Server类

package com.itchinajie.d6_tcp3;import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
* 目标:完成TCP通信快速入门-服务端开发:实现1发1收。
* */
public class Server {public static void main(String[] args) throws Exception {System.out.println("---服务端启动成功----");//1、创建ServerSocket的对象,同时为服务端注册端口。ServerSocket serverSocket =  new ServerSocket(8888);while (true) {//2、使用serverSocket.对象,调用一个accept方法,等待客户端的连接请求Socket socket = serverSocket.accept();System.out.println("有人上线了:" + socket.getRemoteSocketAddress());//3、把这个客户端对应的socket.通信管道,交给一个独立的线程负责处理。new ServerReaderThread(socket).start();}}
}

 ServerReaderThread类

package com.itchinajie.d6_tcp3;import java.io.DataInputStream;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
* 目标:完成TCP通信快速入门-服务端开发:实现1发1收。
* */
public class Server {public static void main(String[] args) throws Exception {System.out.println("---服务端启动成功----");//1、创建ServerSocket的对象,同时为服务端注册端口。ServerSocket serverSocket =  new ServerSocket(8888);while (true) {//2、使用serverSocket.对象,调用一个accept方法,等待客户端的连接请求Socket socket = serverSocket.accept();System.out.println("有人上线了:" + socket.getRemoteSocketAddress());//3、把这个客户端对应的socket.通信管道,交给一个独立的线程负责处理。new ServerReaderThread(socket).start();}}
}

TCP通信-综合案例

即时通信-群聊(端口转发思想)

在服务端定义一个集合用来存储所有管道,方便后续通信,再将离线的管道在集合中抹掉。

Client类

这里要创建一个独立的线程,负责随机从socket中接收服务端发送过来的消息。
        new ClientReaderThread(socket).start();

package com.itchinajie.d7_tcp4;import java.io.DataOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;/*
* 目标:完成TCP通信快速入门-客户端开发:实现多发多收
* */
public class Client {public static void main(String[] args) throws Exception {//1、创建Socket对象,并同时请求与服务端程序的连接Socket socket = new Socket("127.0.0.1",8888);//创建一个独立的线程,负责随机从socket中接收服务端发送过来的消息。new ClientReaderThread(socket).start();//2、从Socket通信管道中得到一个字节输出流,用来发数据给服务端程序。OutputStream os = socket.getOutputStream();//3、把低级的字节输出流包装成数据输出流DataOutputStream dos = new DataOutputStream(os);Scanner sc = new Scanner(System.in);while (true) {System.out.println("请说:");String msg = sc.nextLine();//一旦发现用户输入的exit命令,就退出客户端if ("exit".equals(msg)) {System.out.println("欢迎下次光临!!");dos.close();socket.close();//释放资源break;//跳出死循环}//4、开始写数据出去了dos.writeUTF(msg);dos.flush();}}
}

 ClientReaderThread类

package com.itchinajie.d7_tcp4;import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;public class ClientReaderThread extends Thread{private Socket socket;public ClientReaderThread(Socket socket){this.socket = socket;}@Overridepublic void run() {try {InputStream is = socket.getInputStream();DataInputStream dis = new DataInputStream(is);while (true) {try {String msg = dis.readUTF();System.out.println(msg);} catch (Exception e) {System.out.println("有人下线了:" + socket.getRemoteSocketAddress());dis.close();socket.close();break;}}} catch (Exception e) {e.printStackTrace();}}
}

Server类

package com.itchinajie.d7_tcp4;import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;/*
* 目标:完成TCP通信快速入门-服务端开发:实现1发1收。
* */
public class Server {public static List<Socket> onLineSockets = new ArrayList<>();public static void main(String[] args) throws Exception {System.out.println("---服务端启动成功----");//1、创建ServerSocket的对象,同时为服务端注册端口。ServerSocket serverSocket =  new ServerSocket(8888);while (true) {//2、使用Socket对象,调用一个accept方法,等待客户端的连接请求Socket socket = serverSocket.accept();onLineSockets.add(socket);System.out.println("有人上线了:" + socket.getRemoteSocketAddress());//3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理。new ServerReaderThread(socket).start();}}
}

 ServerReaderThread类

package com.itchinajie.d7_tcp4;import java.io.*;
import java.net.Socket;public class ServerReaderThread extends Thread{private Socket socket;public ServerReaderThread(Socket socket){this.socket = socket;}@Overridepublic void run() {try {InputStream is = socket.getInputStream();DataInputStream dis = new DataInputStream(is);while (true) {try {String msg = dis.readUTF();System.out.println(msg);//把这个消息分发给全部客户进行接收sendMsgToAll(msg);} catch (Exception e) {System.out.println("有人下线了:" + socket.getRemoteSocketAddress());Server.onLineSockets.remove(socket);dis.close();socket.close();break;}}} catch (Exception e) {e.printStackTrace();}}private void sendMsgToAll(String msg) throws Exception {//发送给全部在线的Socket管道接收。for (Socket onLineSocket : Server.onLineSockets) {OutputStream os = onLineSocket.getOutputStream();DataOutputStream dos = new DataOutputStream(os);dos.writeUTF(msg);dos.flush();}}
}

实现一个简易版的BS架构

BS架构的基本原理

 Server类

package com.itchinajie.d8_tcp5;import com.itchinajie.d7_tcp4.ServerReaderThread;import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;/*
* 目标:完成TCP通信快速入门-服务端开发:实现1发1收。
* */
public class Server {public static List<Socket> onLineSockets = new ArrayList<>();public static void main(String[] args) throws Exception {System.out.println("---服务端启动成功----");//1、创建ServerSocket的对象,同时为服务端注册端口。ServerSocket serverSocket =  new ServerSocket(8080);while (true) {//2、使用Socket对象,调用一个accept方法,等待客户端的连接请求Socket socket = serverSocket.accept();System.out.println("有人上线了:" + socket.getRemoteSocketAddress());//3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理。new ServerReaderThread(socket).start();}}
}

  ServerReaderThread类

package com.itchinajie.d8_tcp5;import com.itchinajie.d7_tcp4.Server;import java.io.*;
import java.net.Socket;public class ServerReaderThread extends Thread{private Socket socket;public ServerReaderThread(Socket socket){this.socket = socket;}@Overridepublic void run() {try {OutputStream os = socket.getOutputStream();PrintStream ps = new PrintStream(os);ps.println("HTTP/1.1 200 OK");ps.println("Content-Type:text/html;charset=UTF-8");ps.println();//必须换行ps.println("<div style='color:red;font-size:120px;text-align:center'>小橙不吻茄子<div>");ps.close();} catch (Exception e) {e.printStackTrace();}}private void sendMsgToAll(String msg) throws Exception {//发送给全部在线的Socket管道接收。for (Socket onLineSocket : Server.onLineSockets) {OutputStream os = onLineSocket.getOutputStream();DataOutputStream dos = new DataOutputStream(os);dos.writeUTF(msg);dos.flush();}}
}

注意:服务器必须给浏览器响应HTTP协议规定的数据格式,否则浏览器不识别返回的数据。

HTTP协议规定:响应给浏览器的数据格式必须满足如下格式:

使用线程地进行优化

Server类

package com.itchinajie.d9_tcp6;import com.itchinajie.d7_tcp4.ServerReaderThread;
import com.sun.source.tree.ArrayAccessTree;import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;/*
* 目标:完成TCP通信快速入门-服务端开发:实现1发1收。
* */
public class Server {public static List<Socket> onLineSockets = new ArrayList<>();public static void main(String[] args) throws Exception {System.out.println("---服务端启动成功----");//1、创建ServerSocket的对象,同时为服务端注册端口。ServerSocket serverSocket =  new ServerSocket(8080);//创建一个线程池,负责处理通信管道的任务ThreadPoolExecutor pool = new ThreadPoolExecutor(16 * 2, 16 * 2, 0, TimeUnit.SECONDS,new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());while (true) {//2、使用Socket对象,调用一个accept方法,等待客户端的连接请求Socket socket = serverSocket.accept();System.out.println("有人上线了:" + socket.getRemoteSocketAddress());//3、把这个客户端对应的socket通信管道,交给一个独立的线程负责处理。pool.execute(new ServerReaderRunnable(socket));}}
}

ServerReaderRunnable类

package com.itchinajie.d9_tcp6;import com.itchinajie.d7_tcp4.Server;import java.io.DataOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;public class ServerReaderRunnable implements Runnable{private Socket socket;public ServerReaderRunnable(Socket socket){this.socket = socket;}@Overridepublic void run() {try {OutputStream os = socket.getOutputStream();PrintStream ps = new PrintStream(os);ps.println("HTTP/1.1 200 OK");ps.println("Content-Type:text/html;charset=UTF-8");ps.println();//必须换行ps.println("<div style='color:red;font-size:120px;text-align:center'>小橙不吻茄子<div>");ps.close();} catch (Exception e) {e.printStackTrace();}}private void sendMsgToAll(String msg) throws Exception {//发送给全部在线的Socket管道接收。for (Socket onLineSocket : Server.onLineSockets) {OutputStream os = onLineSocket.getOutputStream();DataOutputStream dos = new DataOutputStream(os);dos.writeUTF(msg);dos.flush();}}
}

这篇关于Java基础入门20:特殊文件、日志技术、多线程、网络通信的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1108681

相关文章

如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解

《如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别详解》:本文主要介绍如何通过海康威视设备网络SDK进行Java二次开发摄像头车牌识别的相关资料,描述了如何使用海康威视设备网络SD... 目录前言开发流程问题和解决方案dll库加载不到的问题老旧版本sdk不兼容的问题关键实现流程总结前言作为

0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型的操作流程

《0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeekR1模型的操作流程》DeepSeekR1模型凭借其强大的自然语言处理能力,在未来具有广阔的应用前景,有望在多个领域发... 目录0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型,3步搞定一个应

SpringBoot中使用 ThreadLocal 进行多线程上下文管理及注意事项小结

《SpringBoot中使用ThreadLocal进行多线程上下文管理及注意事项小结》本文详细介绍了ThreadLocal的原理、使用场景和示例代码,并在SpringBoot中使用ThreadLo... 目录前言技术积累1.什么是 ThreadLocal2. ThreadLocal 的原理2.1 线程隔离2

springboot将lib和jar分离的操作方法

《springboot将lib和jar分离的操作方法》本文介绍了如何通过优化pom.xml配置来减小SpringBoot项目的jar包大小,主要通过使用spring-boot-maven-plugin... 遇到一个问题,就是每次maven package或者maven install后target中的ja

Java中八大包装类举例详解(通俗易懂)

《Java中八大包装类举例详解(通俗易懂)》:本文主要介绍Java中的包装类,包括它们的作用、特点、用途以及如何进行装箱和拆箱,包装类还提供了许多实用方法,如转换、获取基本类型值、比较和类型检测,... 目录一、包装类(Wrapper Class)1、简要介绍2、包装类特点3、包装类用途二、装箱和拆箱1、装

如何利用Java获取当天的开始和结束时间

《如何利用Java获取当天的开始和结束时间》:本文主要介绍如何使用Java8的LocalDate和LocalDateTime类获取指定日期的开始和结束时间,展示了如何通过这些类进行日期和时间的处... 目录前言1. Java日期时间API概述2. 获取当天的开始和结束时间代码解析运行结果3. 总结前言在J

Java深度学习库DJL实现Python的NumPy方式

《Java深度学习库DJL实现Python的NumPy方式》本文介绍了DJL库的背景和基本功能,包括NDArray的创建、数学运算、数据获取和设置等,同时,还展示了如何使用NDArray进行数据预处理... 目录1 NDArray 的背景介绍1.1 架构2 JavaDJL使用2.1 安装DJL2.2 基本操

最长公共子序列问题的深度分析与Java实现方式

《最长公共子序列问题的深度分析与Java实现方式》本文详细介绍了最长公共子序列(LCS)问题,包括其概念、暴力解法、动态规划解法,并提供了Java代码实现,暴力解法虽然简单,但在大数据处理中效率较低,... 目录最长公共子序列问题概述问题理解与示例分析暴力解法思路与示例代码动态规划解法DP 表的构建与意义动

Java多线程父线程向子线程传值问题及解决

《Java多线程父线程向子线程传值问题及解决》文章总结了5种解决父子之间数据传递困扰的解决方案,包括ThreadLocal+TaskDecorator、UserUtils、CustomTaskDeco... 目录1 背景2 ThreadLocal+TaskDecorator3 RequestContextH

关于Spring @Bean 相同加载顺序不同结果不同的问题记录

《关于Spring@Bean相同加载顺序不同结果不同的问题记录》本文主要探讨了在Spring5.1.3.RELEASE版本下,当有两个全注解类定义相同类型的Bean时,由于加载顺序不同,最终生成的... 目录问题说明测试输出1测试输出2@Bean注解的BeanDefiChina编程nition加入时机总结问题说明