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

news2024/11/25 7:12:52

特殊文件

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{
    @Override
    public 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方法

    @Override
    public 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() {
            @Override
            public void run() {
                for (int i = 1; i <= 5 ; i++) {
                    System.out.println("子线程1输出:" + i);
                }
            }
        };
        new Thread(target).start();

        //简化接形式1:使用匿名内部类
        new Thread(new Runnable() {
            @Override
            public 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方法
    @Override
    public 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);//为当前线程设置名字
    }
    @Override
    public 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-0

        Thread 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());//main

        for (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);
            //休眠5s
            if(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;

    }
    @Override
    public 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{
    @Override
    public 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;
    @Override
    public 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();
    //整数修改的乐观锁:原子类实现的
    @Override
    public 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;
    }

    @Override
    public 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.com
        System.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];//64KB
        DatagramPacket 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];//64KB
        DatagramPacket 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;
    }

    @Override
    public 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;
    }

    @Override
    public 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;
    }

    @Override
    public 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;
    }

    @Override
    public 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://www.coloradmin.cn/o/2075385.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【STM32】电容触摸按键

电容按键就是酷&#xff0c;但据我使用过电容按键版的洗澡计费机子后&#xff0c;一生黑&#xff08;湿手优化没做好的电容按键简直稀碎&#xff09;。 大部分图片来源&#xff1a;正点原子HAL库课程 专栏目录&#xff1a;记录自己的嵌入式学习之路-CSDN博客 目录 1 触摸按…

IPv4和IPv6的区别是什么?什么是局域网和广域网,公网IP和私有IP?

文章目录 1.基本网络2.局域网3.广域网4.IPv4与NAT5.公网IP和私有IP6.IPv6 1.基本网络 我们都知道计算机的数据都是存在各自硬盘中的,与其他计算机之间没有人任务关系. 假设计算机A需要给计算机B发送数据,可以选择使用U盘这类移动存储数据来拷贝数据来实现数据交互,但是这样一…

惊艳开源界!20.6K+星标瞩目,打造高性能LLM(大型语言模型)应用的开发平台

项目简介 Dify 是一个开源的LLM&#xff08;大型语言模型&#xff09;应用开发平台&#xff0c;它通过直观的界面结合了AI工作流程、RAG&#xff08;检索-分析-生成&#xff09;管道、代理功能、模型管理、可观察性特性等&#xff0c;使用户能够快速从原型设计转向产品生产。 …

一起搭WPF之列表数据绑定

一起搭WPF之列表数据绑定 1 前言2 数据绑定2.1 前端2.2 后端实现2.2.1 界面后台2.2.2 模型与逻辑 3 问题3.2 解决 总结 1 前言 之前已经简单介绍了列表的大致设计&#xff0c;在设计完列表界面后&#xff0c;我们可以开展列表的数据绑定&#xff0c;在前端显示我们的数据&…

【问题处理】前端Vue项目遇到的一些问题及处理方式

每次新整一个项目的时候&#xff0c;开端总是会遇到各种奇奇怪怪的问题&#xff0c;一步一坎的感觉&#xff0c;但是没关系&#xff0c;遇到了就一步一步去解决&#xff0c;当把所有问题都处理后&#xff0c;成功运行起来&#xff0c;就会突然很有成就感&#xff0c;一切都是那…

大模型嵌入向量Embeddings

版权声明 本文原创作者:谷哥的小弟作者博客地址:http://blog.csdn.net/lfdfhlEmbeddings概述 嵌入(Embeddings)概述 基本概念 嵌入,或称为Embeddings,在机器学习领域中扮演着至关重要的角色。它是一种将离散数据元素,如文本中的单词或图像中的像素点,映射到连续的向量…

虹科方案 | 疫苗冷链温度监测解决方案

通过WHO PQS标准的支持和稳定性预算的应用&#xff0c;我们可以更好地保障疫苗在全球范围内的安全运输和储存&#xff0c;接下来让我们了解一下既能计算药品剩余稳定性预算&#xff0c;又符合WHO PQS预认证的疫苗冷链温度监测解决方案。 疫苗冷链温度监测解决方案 根据WHO和《…

dpdk解析报文协议-基于l2fwd

dpdk解析报文协议-基于l2fwd 0 前置条件 1、这里需要两台虚拟机&#xff0c;配置了相同的虚拟网络&#xff0c;可以通过tcpreplay在一台虚拟机回放报文&#xff0c;在另一台虚拟机通过tcpdump -i 网卡名 捕获到。 具体配置可参考https://www.jb51.net/server/2946942fw.htm 2…

IP SSL:最快捷的安全证书

在这个数字化时代&#xff0c;企业面临着前所未有的挑战——如何在保证业务高效运行的同时保护其核心资产免受网络威胁。随着网络安全事件频发&#xff0c;企业和个人对数据安全的关注度达到了前所未有的高度。在此背景下&#xff0c;IP SSL&#xff08;Internet Protocol Secu…

Python分布式任务处理库之dramatiq使用详解

概要 在现代 Web 应用和数据处理任务中,异步任务处理是一个至关重要的部分。Dramatiq 是一个用于分布式任务处理的 Python 库,旨在提供简单、可靠的任务队列解决方案。与其他任务队列库相比,Dramatiq 更加轻量级,且易于上手。它的设计目标是帮助开发者轻松地将耗时的任务放…

【开源分享】在线客服系统PHP源码 带搭建教程

系统的主要特色功能 自动回复和机器人知识库&#xff1a;通过后台设置机器人知识库&#xff0c;系统可以根据关键词自动回复用户&#xff0c;提高响应速度和服务效率。 内容过滤&#xff1a;支持设置违禁词&#xff0c;避免接收包含不良信息的用户消息&#xff0c;维护平台健…

海康VisionMaster使用学习笔记17-定位项目误差分析及精度提高

定位问题排查步骤 机构及成像排查 标定过程排查 标定数据质量排查 标定结果排查 示教过程排查 注意事项总结

Kompose工具:转换Compose项目为K8S项目

Docker与Kubernetes系列 转换Compose项目为K8S项目 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at CSDN: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:https://blog.cs…

光储电站联合配置!多种调度模式下的光储电站经济性最优储能容量配置分析程序代码!

前言 不同于光伏独立系统以平衡负荷电量为目标&#xff0c;光伏电站需要跟踪调度中心下达的计划出力曲线。光伏发电存在随机性和波动性的特点&#xff0c;大规模集中式光伏电站集群引起出力变化&#xff0c;并且其有功功率上调节能力较弱&#xff0c;将大量占用系统备用资源&a…

MacOS安装Axure10

MacOS安装Axure10 小阿呜有话说一、 软件下载&安装与激活二、Axure10软件汉化 叮嘟&#xff01;这里是小啊呜的学习课程资料整理。好记性不如烂笔头&#xff0c;今天也是努力进步的一天。一起加油进阶吧&#xff01; 小阿呜有话说 前不久换了新电脑&#xff0c;需要重新安…

文心快码Baidu Comate 帮你解大厂面试题:spring如何实现交叉依赖的注入?

&#x1f50d;【大厂面试真题】系列&#xff0c;带你攻克大厂面试真题&#xff0c;秒变offer收割机&#xff01; ❓今日问题&#xff1a;在8g内存的机器&#xff0c;能否启动一个7G堆大小的java进程&#xff1f; ❤️一起看看文心快码Baidu Comate给出的答案吧&#xff01;如…

设计模式2个黄鹂鸣翠柳-《分析模式》漫谈23

DDD领域驱动设计批评文集 做强化自测题获得“软件方法建模师”称号 《软件方法》各章合集 “Analysis Patterns”的第一章有这么一句&#xff1a; The "Gang of Four" book has had much more influence in software patterns than Alexanders work, and three o…

Linux随记(十一)(部署flink-1.16.3、jdk-11.0.19、zookeeper-3.4.13、kafka_2.12-2.2.2)

一、部署flink-1.16.3、jdk-11.0.19、zookeeper-3.4.13、kafka_2.12-2.2.2 #软件下载 https://archive.apache.org/dist/kafka/2.2.2/kafka_2.12-2.2.2.tgz https://archive.apache.org/dist/zookeeper/zookeeper-3.4.13/zookeeper-3.4.13.tar.gz https://archive.apache.org/…

油价波动加剧:需求忧虑与OPEC+增产决策成焦点

周五油价反弹难掩周度跌势 尽管周五油价在美联储降息预期的提振下大幅上扬&#xff0c;但本周整体油价仍录得下跌。WTI原油和布伦特原油分别下跌2.4%和0.83%&#xff0c;显示出市场对全球经济前景及原油需求的担忧。 OPEC增产决策悬而未决 OPEC成员国正面临增产决策的关键时刻。…

USB3.2 摘录(八)

系列文章目录 USB3.2 摘录&#xff08;一&#xff09; USB3.2 摘录&#xff08;二&#xff09; USB3.2 摘录&#xff08;三&#xff09; USB3.2 摘录&#xff08;四&#xff09; USB3.2 摘录&#xff08;五&#xff09; USB3.2 摘录&#xff08;六&#xff09; USB3.2 摘录&…