javaEE进阶小结与回顾(六)

线程通信(了解)

概念

所谓线程通信就是线程间互相发送消息,让线程间相互配合.线程必须先实现同步,才能互相通信

形式

  • 线程间的消息,是通过共享的锁对象发送的
  • 定义一个标记,每个线程根据标记决定是等待还是干活

Object类的等待和唤醒方法

void wait()
让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或notifyAll()方法
void notify()
唤醒正在等待的单个线程
void notifyAll()
唤醒正在等待的所有线程

注意:

等待和唤醒方法应该使用当前同步锁对象进行调用

线程池(重点)

线程池概述

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

不使用线程池的问题

  • 每发起请求,后台就创建一个新线程来处理,下次新任务又要创建新线程,系统创建线程的成本比较高,因为它涉及到与操作系统交互

  • 当程序中需要创建大量生存期很短暂的线程时,频繁的创建和销毁线程对系统的资源消耗有可能大于业务处理时对系统资源的消耗,"舍本逐末",影响系统性能

任务接口

  • Runnable
  • Callable

线程池实现的API,参数说明

谁代表线程池

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

如何得到线程池对象

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

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

ThreadPoolExecutor构造器参数说明

public ThreadPoolExecutor(int corePoolSize,
             int maximumPoolSize,
             long keepAliveTime,
             TimeUnit unit,
             BlockingQueue<Runnable> workQueue,
             ThreadFactory threadFactory,
             RejectedExecutionHandler handler)
  • 参数一:指定线程池的核心线程数量:corePoolSize
    不能小于0

  • 参数二:指定线程池可支持的最大线程数:maximumPoolSize
    最大数量>=核心线程数量

  • 参数三:指定临时线程的最大空闲时间:keepAliveTime
    不能小于0

  • 参数四:指定时间的单位(秒,分,时,天):unit
    时间单位

  • 参数五:指定任务队列:workQueue
    不能为null

  • 参数六:指定用哪个线程工厂创建线程:threadFactory
    不能为null

  • 参数七:指定线程忙,任务满的时候,新任务来了怎么办:handler
    不能为null

### 线程池处理Runnable任务

ThreadPoolExecutor创建线程池对象示例

ExecutorService pools = new ThreadPoolExecutor(3,5,8,
                   TimeUnit.SECONDS,
                   new ArrayBlockingQueue<>(6),
                   Executors.defaultThreadFactory(),
                   new ThreadPoolExecutor.AbortPolicy());

ExecutorService常用方法

void execute(Runnable command)
执行任务/命令,没有返回值,一般用来执行Runnable任务
Future<T> submit(Callable<T> task)
执行任务,返回未来任务对象获取线程结果,一般拿来执行Callable任务
void shutdown()
等任务执行完毕后关闭线程池
List<Runnable> shutdownNow()
立刻关闭,停止正在执行的任务,并返回队列中未执行的任务

新任务拒绝策略

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

常见问题

  • 临时线程什么时候创建
    新任务提交时,核心线程在忙,任务队列排满,并且可以创建临时线程,此时会创建

  • 什么时候会开始拒绝任务
    核心线程和临时线程都忙,任务队列满了,新任务提交时会拒绝

Executors工具类实现线程池

  • Executors:线程池的工具类通过调用方法返回不同类型的线程池对象
    底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的

常用方法

1.public static ExecutorService newCashedThreadPool()
线程数量随着任务增加而增加,如果线程任务执行完毕且空闲了一段时间则会被回收掉
2.public static ExecutorService newFixedThreadPool(int nThreads)
创建固定线程数量的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程替代它
3.public static ExecutorService newSingleThreadExecutor()
创建只有一个线程的线程池对象,如果该线程出现异常而结束,那么线程池也会补充一个新线程
4.public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务

  • 2,3在使用Executors可能存在的风险
    允许请求的任务队列长度是Integer.MAX_VALUE,可能出现OOM错误(java.lang.OutOfMemoryError)

  • 1,4在使用Executors可能存在的风险
    创建的线程数量最大上限是Integer.MAX_VALUE,线程数可能会随任务1:1增长,也可能出现OOM错误

  • 大型互联网场景的线程池方案,不适合用Executors做
    建议使用ThreadPoolExecutor来指定线程池参数,这样可以明确线程池的运行规则,规避资源耗尽的风险

定时器

一种控制任务延时调用或者周期调用的技术,作用如闹钟,定时邮件

实现方式

Timer

构造器

public Timer()
创建Timer定时器对象

方法

public void schedule(TimerTask task,long delay,long period)
开启一个定时器,按照计划处理TimerTask任务

特点和存在的问题
  • Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入
  • 可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行

ScheduledExecutorService

jdk1.5中引入了并发包,目的为了弥补Timer的缺陷,ScheduledExecutorService内部为线程池

方法

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit)
周期调度方法

优点

基于线程池,某个任务的执行情况不会影响其他定时任务的执行

并发,并行

进程

正在运行的程序就是一个独立进程,线程属于进程

并发的理解

  • CPU同时处理线程的数量有限

  • CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们感觉这些线程在同时执行,这就是并发

并行的理解

在同一时刻上,同时有多个线程在被多个CPU核心处理(真正的同时)
实际场景,并发多,资源永远是不够的

线程生命周期

线程的状态

  • 也就是线程从生到死的过程,以及中间经历的各种状态及状态转换
  • 理解线程状态有利于提升并发编程的理解能力

Java线程的状态(6种)

定义在Thread类的内部枚举类中
public class Thread{
   ...
   public enum State{
   NEW,
   RUNNABLE,
   BLOCKED,
   WAITING,
   TIMED_WAITING,
   TERMINATED;
  }
  ...
}
NEW新建
  • 线程刚被创建,但未启动
  • 创建线程对象
Runnable可运行
  • 线程已经调用了start()等待CPU调度
  • start方法
Blocked锁阻塞
  • 线程在执行的时候未竞争到锁对象,则该线程进入Blocked状态
  • 无法获得锁对象
Waiting无限等待
  • 一个线程进入Waiting状态,另一个线程调用notify或者notifyAll方法才能唤醒
  • wait方法
Timed Waiting计时等待
  • 同Waiting状态,有几个方法有超时参数,调用他们将进入Timed Wating状态,带有超时参数的常用方法有Thread.sleep Object.wait

  • sleep方法

Teminated被终止
  • 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡
  • run()方法结束/异常未捕获

单例设计模式

概念

设计模式

前人总结一些解决不同问题的方案,这些方案就是设计模式

单列模式

一种让一个类只能产生一个对象的方案

单列场景作用

任务管理器对象我们只需要一个就可以解决问题,这样可以节省内存空间

实现方式

饿汉模式

在类加载的时候把唯一对象创建出来

实现步骤
  • 把类的构造器私有
  • 定义一个本类类型的静态属性,并且new一个本类对象进行赋值
  • 定义一个静态方法,返回上面创建的对象

懒汉模式

  • 在真正需要该对象的时候,才去创建一个对象(延迟加载对象)
  • 可能要同步
实现步骤
  • 把构造器私有
  • 定义一个用于记住当前对象的静态变量,默认值null
  • 提供一个返回单例对象的静态方法,在方法中new对象

枚举

概述

Java中一种特殊类型

格式

修饰符  enum  枚举名称{
    第一行都是罗列枚举类实例的名称
}

enum  Season{
     SPRING,SUMMER,AUTUMN,WINTER;
}

手写一个枚举类

Compiled from "Season.java"
public final class Season extends java.lang.Enum<Season>{
     public static final Season SPRING = new Season();
     public static final Season SUMMER = new Season();
     public static final Season AUTUMN = new Season();
     public static final Season WINTER = new Season();
     public static Season[] values();
     public static Season valueOf(java.lang.String);
}

特征

  • 枚举类的第一行默认都是罗列枚举对象的名称
  • 都是继承了枚举类型:java.lang.Enum
  • 枚举都是最终类,不可以被继承
  • 构造器都是私有的,枚举对外不能创建对象
  • 枚举类相当于是多例模式

网络编程:通过编程实现网络上不同设备之间的数据交换

常见网络通信模式

  1. Client-Server(C/S模式)
  2. Browser/Server(B/S模式)
  • 分布性:可以随时随地进行查询和浏览等业务
  • 功能业务扩展比较方便:增加服务器的功能,就能增加浏览器端的功能
  • 维护简单方便:改变服务器端数据即可实现所有用户同步更新
  • 开发简单,共享性强,成本低,数据可以持久存储在服务器端而不必担心数据的丢失
    BS的优势即CS的劣势

网络通信三要素

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

全称"互联网协议地址",是分配上网设备的唯一标识,常见IP分类为IPv4(4字节)和IPv6(16字节)

IP地址形式

  • 公网地址和私有地址(局域网)
  • 192.168开头的就是常见局域网地址,范围即为192.168.0.0--192.168.255.255 专门为组织机构内部使用

IP常用命令

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

特殊IP地址

本机IP:127.0.0.1或者localhost:称为回送地址也可称本地回环地址,只会寻找当前所在本机

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

标识正在计算机设备上运行的进程(程序),被规定为一个16位的二进制,范围是0-65535

端口类型

  • 周知端口:0-1023,被预先定义的知名应用占用(HTTP占用80,FTP占用21)
  • 注册端口:1024-49151,分配给用户进程或某些应用程序(Tomcat占用8080,MySQL占用3306)
  • 动态端口:49152-65535.之所以称为动态端口,是因为它一般不固定分配某种进程,而是动态分配
    注意:开发程序要选择注册端口,且一个设备中不能出现两个程序的端口号一样,否则出错

协议:数据在网络中传输的规则,常见的协议有UDP协议和TCP协议

连接和通信数据的规则被称为网络通信协议

有两套参考模型

  • OSI参考模型
    过于理想化,未能广泛推广

  • TCP/IP参考模型
    事实上的国际标准

传输层的2个常见协议

TCP(Transmission Control Protocol):传输控制协议
特点
  • 建立连接(三次握手),是一种面向连接的可靠通信协议(安全,效率稍低)
  • 形成数据传输的通道
  • 可以发送大量数据
    文件下载,金融等数据通信
UDP(User Datagram Protocol):用户数据报协议
特点
  • UDP是一种无连接,不可靠的传输协议(不安全,效率较高)
  • 数据打包发送
  • 每个数据包64KB以内
    语音视频会话

IP地址操作类InetAddress

public static InetAddress getLocalHost()
返回本主机的地址对象
public static InetAddress getByName()
得到指定主机的IP地址对象,参数是域名或者IP地址
public String getHostName()
获取此IP地址的主机名
public String getHostAddress()
返回IP地址字符串
public boolean isReachable(int timeout)
在指定毫秒内连通该IP地址对应的主机,连通返回true

UDP通信

概念

网络编程也叫Socket编程,网络通信也叫Socket通信,Socket(套接字)=ip+端口

DatagramPacket

构造器

public DatagramPacket(byte[] buf,int length,InetAddress, int port)
创建发送端数据包对象

  • buf:要发送的内容,字节数组
  • length:要发送内容字节长度
  • address:接收端的IP地址对象
  • port:接收端的端口号

public DatagramPacket(byte[] buf , int length)
创建接收端的数据包对象

  • buf:用来存储接收的内容
  • length:能够接收内容的长度

常用方法

public int getLength()
获得实际接收到的字节个数

DatagramSocket:发送端和接收端对象(人)

构造器

public DatagramSocket()
创建发送端的Socket对象,系统会随机分配一个端口号
public DatagramSocket(int port)
创建接收端的Socket对象并指定端口号

方法

public void send(DatagramPacket dp)
发送数据包
public void receive(DatagramPacket p)
接收数据包

客户端步骤

  1. 创建DatagramSocket对象(发送端对象)
  2. 创建DatagramPacket对象封装需要发送的数据(数据包对象)
  3. 使用DatagramSocket对象的send方法传入DatagramPacket对象
  4. 释放资源

服务端步骤

  1. 创建DatagramSocket对象并指定端口(接收端对象)
  2. 创建DatagramPacket对象接收数据(数据包对象)
  3. 使用DatagramSocket对象的receive方法传入DatagramPacket对象
  4. 释放资源

TCP通信

在java中主要是使用java.net.Socket类实现通信,底层即是使用了TCP协议

Socket

构造器

public Socket(String host,int port)
创建发送端的Socket对象与服务器连接,参数为服务端程序的ip和端口号

方法

OutputStream getOutputStream()
获得字节输出流对象
InputStream getInputStream()
获得字节输入流对象

客户端发送消息步骤

  1. 创建客户端的Socket对象,请求与服务端的连接
  2. 使用Socket对象调用getOutputStream()方法得到字节输出流
  3. 使用字节输出流完成数据的发送
  4. 释放资源:关闭Socket管道

ServerSocket

构造器

public ServerSocket(int port)
注册服务端端口

方法

public Socket accept()

  • 等待接收客户端的Socket通信连接
  • 连接成功返回Socket对象,与客户端建立端到端的通信

服务端接收消息步骤

  1. 创建ServerSocket对象,注册服务端端口
  2. 调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象
  3. 通过Socket对象调用getInputStream()方法得到字节输入流,完成数据的接收
  4. 释放资源:关闭Socket管道

TCP应用

TCP通信-多发多收消息

循环发送和接收的动作,不关闭管道

TCP通信-同时接受多个客户端消息

  • 服务端单线程则不可以,每次只能处理一个客户端的消息
  • 需要每接收一个Socket通信后分配一个独立的线程负责处理消息

TCP通信-使用线程池优化

主线程死循环接收Socket连接

目前通信架构的问题

  • 客户端服务端线程模型是N-N关系
  • 客户端并发越多,系统瘫痪越快

服务端复用线程处理多个客户端,可以避免瘫痪,但只适用通信时长较短场景

TCP通信实战案例-上传文件

  1. 客户端控制台输入需要上传文件路径,并获取文件名称
  2. 客户端服务端建立TCP连接后,将文件名称和文件的字节数据发送到服务端
  3. 服务端接收到文件名称和文件的字节数据,将文件存储在本地的upload文件夹中
  4. 关闭连接,释放资源

TCP通信实战案例-模拟BS系统

  • 浏览器访问服务端,不需要开发客户端
  • 服务器必须给浏览器响应HTTP协议格式的数据,否则浏览器不识别
posted @ 2023-04-15 07:53  zFlame_5020  阅读(19)  评论(0)    收藏  举报