【学习日记2023.4.13】之线程池_网络编程(UDP编程)

文章目录

  • 1. 线程池
    • 1.1 认识线程池
    • 1.2 `ThreadPoolExecutor`创建线程池
      • 七个参数
      • TimeUnit.时间单位详解
    • 1.3 ExecutorService中的常见方法
      • 线程池处理Runnable任务
      • 任务拒绝策略
      • 线程池处理Callable任务
    • 1.4 Executors工具类工具类创建线程池
    • 1.5 线程池小结
  • 2. 网络编程
    • 2.1 概述
    • 2.2 IP
      • 2.2.1 分类
      • 2.2.2 域名
    • 2.3 InetAddress
    • 2.4 端口
    • 2.5 协议
      • 网络模型
      • UDP协议
      • TCP协议
  • 3. UDP编程
    • 3.1 一发一收-客户端发消息
    • 3.2 一发一收-服务端收消息
    • 3.3 多发多收
  • 4. 总结
    • 4.1 线程及线程池
    • 4.2 网络编程基础知识及UDP编程总结

1. 线程池

1.1 认识线程池

  • 线程池: 是一个可以复用线程的技术
  • 不使用线程池的问题
    • 用户每发起一个请求,后台就需要创建一个新线程来处理,下次新任务来了肯定又要创建新线程处理的,而创建新线程的销是很大的,并且请求过多时,肯定会产生大量的线程出来,这样会严重影响系统的性能。
  • 线程池: ExecutorService接口,ThreadPoolExecutor实现类
    • 创建线程池的第一种方式: 创建ThreadPoolExecutor类的对象
    • 创建线程池的第二种方式: Executors工具类

1.2 ThreadPoolExecutor创建线程池

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

七个参数

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit,BlockingQueue workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) 
参数 说明 理解
参数一:corePoolSize 指定线程池的核心线程的数量 正式工 3
参数二:maximumPoolSize 指定线程池的最大线程数量 最大员工数:5 临时工:2
参数三:keepAliveTime 指定临时线程的存活时间。 临时工空闲多久被开除
参数四:unit 指定临时线程存活的时间单位(秒、分、时、天等)
参数五:workQueue 指定线程池的任务队列。 客人排队的地方
参数六:threadFactory 指定线程池的线程工厂。 负责招聘员工的(hr)
参数七:handler 指定线程池的任务拒绝策略(线程都在忙,任务队列也满了的时候,新任务来了该怎么处理) 忙不过来咋办?
public class Demo {
    public static void main(String[] args) {
        // JDK5 出现的接口  ExecutorService  表示线程池
        // 实现类 ThreadPoolExecutor
        ExecutorService pool = new ThreadPoolExecutor(
                3, // 核心线程数量
                5,// 最大线程数量 (核心线程3 + 临时线程2)
                10, // 临时线程空闲多长时间,销毁
                TimeUnit.SECONDS, // 时间单位(纳秒,微秒,秒,分钟,小时,天)
                new ArrayBlockingQueue<>(5), // 任务队列,需要指定队列的大小, 最多可以有5个任务在这里排队
                Executors.defaultThreadFactory(), // 使用JDK线程池中提供的默认的线程工厂
                new ThreadPoolExecutor.AbortPolicy() // 所有的线程都在,队列也满了,该怎么处理
        );
    }
}

TimeUnit.时间单位详解

【学习日记2023.4.13】之线程池_网络编程(UDP编程)_第1张图片

1.3 ExecutorService中的常见方法

方法 说明
void execute(Runnable command) 执行 Runnable 任务
Future submit(Callable task) 执行 Callable 任务,返回未来任务对象,用于获取线程返回的结果
void shutdown() 等全部任务执行完毕后,再关闭线程池!
ListshutdownNow() 立刻关闭线程池,停止正在执行的任务,并返回队列中未执行的任务

线程池处理Runnable任务

public class MyRunnable implements Runnable {
 @Override
 public void run() {
     // 线程开启后执行的任务
     System.out.println(Thread.currentThread().getName() + "执行了~");
     try {
         Thread.sleep(5000);
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
 }
}
/*
 ExecutorService提供常见的方法
     提交Runnable任务
 演示复用线程特点

 线程池使用的注意事项
     1.问题: 核心线程都在忙,再有新的任务,直接创建临时线程,还是排队?
       回答: 优先放到队列中排队,如果队列满了 再创建临时线程
     2.问题: 什么时候触发任务拒绝策略
       回答: 核心线程,临时线程都在忙,队列也满了,再提交任务
 线程池提供了4种拒绝策略
*/
public class Demo {
 public static void main(String[] args) {
     ExecutorService pool = new ThreadPoolExecutor(
             3, // 核心线程数量
             5,// 最大线程数量 (核心线程3 + 临时线程2)
             10, // 临时线程空闲多长时间,销毁
             TimeUnit.SECONDS, // 时间单位(纳秒,微秒,秒,分钟,小时,天)
             new ArrayBlockingQueue<>(5), // 任务队列,需要指定队列的大小, 最多可以有5个任务在这里排队
             Executors.defaultThreadFactory(), // 使用JDK线程池中提供的默认的线程工厂
             new ThreadPoolExecutor.AbortPolicy() // 所有的线程都在,队列也满了,该怎么处理
     );

     // Runnable实现类对象,封装着线程开启执行的任务
     MyRunnable m = new MyRunnable();
     pool.execute(m); // 创建了一个核心线程,处理任务
     pool.execute(m); // 创建了一个核心线程,处理任务
     pool.execute(m); // 创建了一个核心线程,处理任务

     pool.execute(m); // 排队
     pool.execute(m); // 排队
     pool.execute(m); // 排队
     pool.execute(m); // 排队
     pool.execute(m); // 排队

     pool.execute(m);
     pool.execute(m);

     pool.execute(m); // 拒绝
     pool.execute(m); // 拒绝

     // 线程池不会主动关闭,需要调用关闭线程池方法(线程池一般不需要关闭)
//        pool.shutdown(); // 等线程池中所有的任务都执行完,再关闭
//        pool.shutdownNow(); // 立即关闭线程池
 }
}

###线程池注意事项

  • 临时线程什么时候创建?
    • 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
  • 什么时候会开始拒绝新任务?
    • 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。

任务拒绝策略

策略 详解
ThreadPoolExecutor.AbortPolicy 丢弃任务并抛出RejectedExecutionException异常。是默认的策略
ThreadPoolExecutor.DiscardPolicy: 丢弃任务,但是不抛出异常 这是不推荐的做法
ThreadPoolExecutor.DiscardOldestPolicy 抛弃队列中等待最久的任务 然后把当前任务加入队列中
ThreadPoolExecutor.CallerRunsPolicy 由主线程负责调用任务的run()方法从而绕过线程池直接执行

线程池处理Callable任务

// 求1~n的和
public class MyCallable implements Callable {
    private int n;

    public MyCallable(int n) {
        this.n = n;
    }

    @Override
    public Long call() throws Exception {
        long sum = 0;
        for (int i = 1; i <= n; i++) {
            sum+=i;
        }
        return sum;
    }
}
/*
    演示使用线程池提交Callable任务  Future<> submit(Callable)
 */
public class Demo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = new ThreadPoolExecutor(
                3,
                5,
                10,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(4)
                // Executors.defaultThreadFactory();
                // new ThreadPoolExecutor.AbortPolicy()
        );

        // 创建MyCallable对象

        MyCallable m1 = new MyCallable(10);
        MyCallable m2 = new MyCallable(20);
        MyCallable m3 = new MyCallable(30);
        MyCallable m4 = new MyCallable(40);
        // 提交4个Callable任务
        Future<Long> f1 = pool.submit(m1);
        Future<Long> f2 = pool.submit(m2);
        Future<Long> f3 = pool.submit(m3);
        Future<Long> f4 = pool.submit(m4);

        System.out.println(f1.get()); // 通过get方法获取线程结束后的返回值
        System.out.println(f2.get()); // 通过get方法获取线程结束后的返回值
        System.out.println(f3.get()); // 通过get方法获取线程结束后的返回值
        System.out.println(f4.get()); // 通过get方法获取线程结束后的返回值


    }
}

1.4 Executors工具类工具类创建线程池

  • 这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象。
  • 注意: 大型并发系统环境中使用Executors如果不注意可能会出现系统风险。
方法 说明
public static ExecutorService newFixedThreadPool(int nThreads) 创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它。
public static ExecutorService newSingleThreadExecutor() 创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池会补充一个新线程。
public static ExecutorService newCachedThreadPool() 线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了60s则会被回收掉。
public static ScheduledExecutorService newCachedThreadPool() 创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务。

1.5 线程池小结

【学习日记2023.4.13】之线程池_网络编程(UDP编程)_第2张图片

2. 网络编程

2.1 概述

  • 什么是网络编程: 可以让设备中的程序与网络上其他设备中的程序进行数据交互(实现网络通信的)。
  • 三要素
    • IP: 是网络中设备的唯一标识 。
    • 端口:应用程序在设备中的唯一标识。
    • 计算机网络中,连接和通信的规则被称为网络通信协议

2.2 IP

  • 互联网协议地址 ,也叫IP地址,是网络中设备的唯一标识 。

2.2.1 分类

  • 分类: ipv4和ipv6

  • ipv4

    • 32位(4字节一组) 2进制
    • 点分十进制表示法 192.168.1.66
      【学习日记2023.4.13】之线程池_网络编程(UDP编程)_第3张图片
  • ipv6

    • 128位(16字节一组) 2进制
    • 冒分十六进制表示法 fe80:0000:0000:0000:589d:eadb:8890:a9bc
      【学习日记2023.4.13】之线程池_网络编程(UDP编程)_第4张图片

2.2.2 域名

【学习日记2023.4.13】之线程池_网络编程(UDP编程)_第5张图片

  • 公网IP,内网IP
    • 公网IP:是可以连接互联网的IP地址;内网IP:也叫局域网IP,只能组织机构内部使用。
    • 192.168. 开头的就是常见的局域网地址,范围即为192.168.0.0–192.168.255.255,专门为组织机构内部使用。
  • 特殊IP
    • 127.0.0.1、localhost:代表本机IP,只会寻找当前所在的主机。
  • IP常用命令:
    • ipconfig:查看本机IP地址。
    • ping IP地址:检查网络是否连通。

2.3 InetAddress

  • 代表IP
方法 说明
public static InetAddress getLocalHost() 获取本机IP,会以一个InetAddress的对象返回
public static InetAddress getByName(String host) 根据ip地址或者域名,返回一个InetAddress对象
public String getHostName() 获取此IP地址对应的主机名
public String getHostAddress() 获取此IP地址对应的ip地址信息
public boolean isReachable(int timeout) 在指定毫秒内,判断主机与该ip对应的主机是否能联通
public class Demo {
    public static void main(String[] args) throws IOException {
        // 1.获取本机的InetAddress对象
        InetAddress ip1 = InetAddress.getLocalHost();
        // 2.获取对象中的主机名
        System.out.println(ip1.getHostName());
        // 3.获取对象中的ip地址
        System.out.println(ip1.getHostAddress());

        // 4.通过ip地址或者域名获取一个InetAddress对象
        InetAddress ip2 = InetAddress.getByName("www.baidu.com");
        System.out.println(ip2.getHostAddress());
        System.out.println(ip2.getHostName());

        // 5.监测InetAddress封装的ip地址和本机在指定的毫秒内是否能够连通
        System.out.println(ip2.isReachable(30));
    }
}

2.4 端口

  • 端口号:应用程序在设备中的唯一标识。取值范围:0~65535
  • 分类
    • 周知端口:0~1023,被预先定义的知名应用占用。(如:HTTP占用80,FTP占用21)
    • 注册端口:1024~49151,分配给用户进程或某些应用程序
    • 动态端口:49152~65535,之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配
  • 注意:我们自己开发的程序一般选择使用注册端口,且一个设备中不能出现两个程序的端口号一样,否则会出错

2.5 协议

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

网络模型

【学习日记2023.4.13】之线程池_网络编程(UDP编程)_第6张图片

UDP协议

  • UDP(User Datagram Protocol):用户数据报协议
  • 特点:无连接、不可靠通信。通讯效率高
  • 不事先建立链接,数据按照数据包发,一包数据包含:
    • 自己的IP、程序端口
    • 目的地IP、程序端口
    • 数据(限制在64KB内)等
  • 发送方不管对放是否在线,数据中间丢失也不管,如果接收方收到数据也不返回确认,故是不可靠的
  • 适用场景: 语音通话,视频直播…
    【学习日记2023.4.13】之线程池_网络编程(UDP编程)_第7张图片

TCP协议

  • TCP(Transmission Control Protocol):传输控制协议

  • 特点:面向连接、可靠通信。通信效率相对不高

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

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

  • 适用场景:网页、文件下载、支付…

  • 三次握手、传输数据确认
    【学习日记2023.4.13】之线程池_网络编程(UDP编程)_第8张图片

  • 四次挥手
    【学习日记2023.4.13】之线程池_网络编程(UDP编程)_第9张图片

3. UDP编程

  • DatagramSocket: 用于创建客户端、服务端
构造方法 说明
public DatagramSocket() 创建客户端的Socket对象,系统会随机分配一个端口号
public DatagramSocket(int port) 创建服务端的Socket对象,并指定端口号
方法 说明
public void send(DatagramPacket dp) 发送数据包
public void receive(DatagramPacket p) 使用数据包接收数据
  • DatagramPacket:数据包对象
构造方法 说明
public DatagramPacket(byte[] buf, int length, InetAddress address, int port) 创建发出去的数据包对象
public DatagramSocket(byte[] buf, int length) 创建用来接收数据的数据包
方法 说明
public int getLength() 获取数据包,实际接收的字节个数

3.1 一发一收-客户端发消息

  • 创建客户端DatagramSocket对象
  • 创建数据包对象,封装要发出的数据
  • 使用数据包发送
  • 释放资源
/*
    客户端程序
 */
public class Client {
    public static void main(String[] args) throws IOException {
        // 1.创建DatagramSocket对象
        DatagramSocket ds = new DatagramSocket();  // 客户端使用随机端口号
        // 参数一 字节数组 数据
        byte[] bytes = "你好吗".getBytes();
        // 参数二 要发送数据字节个数
        int length = bytes.length;
        // 参数三 服务端ip地址对象
        InetAddress ip = InetAddress.getLocalHost(); // 由于服务端程序也是在本机 所以使用本机的ip
        // 参数四 服务端程序的端口号
        int port = 10000;
        // 2.创建包裹对象用来装数据(DatagramPacket)
        DatagramPacket dp = new DatagramPacket(bytes,length,ip,port);
        // 3.send(包裹)方法发送
        ds.send(dp);
        // 4.释放资源
        ds.close();
    }
}

3.2 一发一收-服务端收消息

  • 创建服务端DatagramSocket对象
  • 创建数据包对象,封装接收到的数据
  • 使用数据包接收
  • 处理数据
  • 释放资源
/*
    服务端程序
    先启动服务端程序,在启动客户端程序
 */
public class Server {
    public static void main(String[] args) throws IOException {
        // 1.创建DatagramSocket对象
        DatagramSocket ds = new DatagramSocket(10000);

        // 参数一: 接收数据的字节数组
        byte[] bytes = new byte[1024 *64];  // 64kb
        // 参数二: 字节数组的大小
        int length = bytes.length;
        // 2.创建数据包对象 准备接收数据
        DatagramPacket dp = new DatagramPacket(bytes,length);

        // 3.接收服务端发送数据
        ds.receive(dp); // 数据就进入到字节数组中了  等着接收
        // 4.处理数据
        // dp.getLength()这一次接收到的实际字节数
        String s = new String(bytes,0,dp.getLength());
        System.out.println(s);

        // 5.释放资源
        ds.close();
    }
}

3.3 多发多收

  • 客户端: 将用户键盘录入的数据发送到服务端,直到用户录入886

  • 服务端: 一直接收客户端发送的数据

  • 服务端: 将接收数据的代码放入循环包裹

/*
    服务端程序
    先启动服务端程序,在启动客户端程序
 */
public class Server {
    public static void main(String[] args) throws IOException {
        // 1.创建DatagramSocket对象
        DatagramSocket ds = new DatagramSocket(10000);

        // 参数一: 接收数据的字节数组
        byte[] bytes = new byte[1024 *64];  // 64kb
        // 参数二: 字节数组的大小
        int length = bytes.length;
        // 2.创建数据包对象 准备接收数据
        DatagramPacket dp = new DatagramPacket(bytes,length);

        while (true) {
            // 3.接收服务端发送数据
            ds.receive(dp); // 数据就进入到字节数组中了  等着接收
            // 4.处理数据
            // dp.getLength()这一次接收到的实际字节数
            String s = new String(bytes,0,dp.getLength());
            InetAddress ip = dp.getAddress();// 获取客户端IP地址
            System.out.println(ip.getHostAddress() +": " +s);
        }

        // 5.释放资源
//        ds.close();
    }
}
  • 客户端
    • 将用户键盘录入的数据发送到服务端
    • 将录入数据和发送数据的代码放入循环中
    • 当用户录入的数据为"886",break
/*
    客户端程序
    1.使用键盘录入,用户录入的数据,发送到服务端
    2.如果用户录入的886 结束客户端的程序
    3.使用while循环包裹录入数据,发送数据的代码
 */
public class Client {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        // 1.创建DatagramSocket对象
        DatagramSocket ds = new DatagramSocket();  // 客户端使用随机端口号
        while (true) {
            // 参数一 字节数组 数据
            String s = sc.nextLine();
            if ("886".equals(s)){
                break;
            }
            byte[] bytes = s.getBytes();
            // 参数二 要发送数据字节个数
            int length = bytes.length;
            // 参数三 服务端ip地址对象
//        InetAddress ip = InetAddress.getByName("其他人ip地址");
            InetAddress ip = InetAddress.getLocalHost(); // 由于服务端程序也是在本机 所以使用本机的ip
            // 参数四 服务端程序的端口号
            int port = 10000;
            // 2.创建包裹对象用来装数据(DatagramPacket)
            DatagramPacket dp = new DatagramPacket(bytes,length,ip,port);
            // 3.send(包裹)方法发送
            ds.send(dp);
        }
        // 4.释放资源
        ds.close();
    }
}

4. 总结

4.1 线程及线程池

【学习日记2023.4.13】之线程池_网络编程(UDP编程)_第10张图片

4.2 网络编程基础知识及UDP编程总结

【学习日记2023.4.13】之线程池_网络编程(UDP编程)_第11张图片

你可能感兴趣的:(#JavaSE进阶,学习,udp,java)