Java进阶4(多线程+网络编程+单元测试+反射+注解+动态代理)

多线程

多线程的实现方案一:继承Thread类

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

创建MyThread类的对象

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

注意 为什么不直接调用run方法,而是调用start启动线程

直接调用run方法会当成普通方法执行 此时相当于还是单线程执行   只有调用start方法才是启动一个新的线程执行

不能把主线任务放在子线程之前  如上图就是把main函数里面的那个  主线程执行输出  放在  t.start()  之前

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

方式一优缺点

优点  编码简单  

缺点  线程类已经继承了Thread,无法继承其他类,不利于扩展

多线程的实现方案二:实现Runnable接口

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

创建MyRunnable任务对象

把MyRunnable任务对象交给Thread处理

方式二优缺点

优点 线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强

缺点 编程多一层对象包装类     如果线程有执行结果是不可以直接返回的(第一种也是一样)

多线程的实现方案二(另一种写法而已):实现Runnable接口(匿名内部类形式)

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

交给Thread处理

调用线程对象start()启动线程

在上面的基础之上继续简化

多线程的实现方案三:利用Callable、FutureTask接口实现

1 得到任务对象    定义类实现Callable接口,重写call方法 ,封装要做的事情

                          用FutureTask把Callable对象封装成线程任务对象

2 把线程任务对象交给Thread处理

3 调用Thread的start方法启动线程,执行任务

4 线程执行完毕后,通过FutureTask的 get 方法获取任务执行的结果

方式三优缺点

优点  线程任务类只是实现接口 可以继续继承类和实现接口  扩展性强 

可以在线程执行完毕后去获取线程执行的结果

缺点 代码复杂

三种方式对比

                                                                            优点                                                               缺点

继承Thread类                                         代码简单,可以直接使用Thread的方法                       由于继承只能单继承所以扩展性差  不能返回线程执                                                                   

                                                                                                                                                  行结果   

实现Runnable接口                                  扩展性好,可以实现多个接口,而且可以继承一个类    代码复杂,不能返回线程执行结果                                                                      

                                                                                                                            

利用Callable、FutureTask接口实现       扩展性好,还可以得到线程执行的结果                       代码复杂

问题引出:当有很多线程在执行的时候,我们怎么区分这些线程呢

此时需要使用Thread的常用方法:getName()  setName()  currentThread()

Thread获取和设置线程名称

String getName()  获取当前线程的名称 默认线程名称是Thread-索引

void setName(String name)将此线程的名称更改为指定的名称  通过构造器也可以设置线程名称

结果

用构造器可以替代setName 来为线程取名

结果

在包装Runnable的时候同时给其起名

Thread类的线程休眠方法

执行到i==3时,会停留3秒,继续执行输出 4  5

线程安全问题

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

比如  夫妻有同一个银行账户 余额10w  然后夫妻双方同时取钱10w,当他们同时判断余额时,可能双方余额都满足,所以同时取会取出20w

         这样银行亏了10w

线程安全问题解决方法---线程同步

线程同步的核心思想

加锁 , 把共享资源进行上锁,每次只能有一个线程进入访问完毕以后解锁,然后其他线程才能进来

1 同步代码块

作用:把出现线程安全问题的核心代码给上锁

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

synchronized(同步锁对象){

              操作共享资源的代码(核心代码)  

}

锁对象要求

理论上 锁对象只要对于当前同时执行的线程来说是同一个对象即可

锁对象使用任意唯一的对象好不好     [就是synchronized("chen") 这里,括号里面可以随便写]

不好,会影响其他无关线程的执行  【 例如银行账户那个,如果是任意唯一对象,当另一个账户也来取钱,导致其需要等待上一个用户】

锁对象的规范要求

规范上:建议使用共享资源作为锁对象

对于实例方法建议使用this作为锁对象

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

实例方法                                                                                                         静态方法

2 同步方法(锁的范围小于同步代码块)

作用 把出现线程安全问题

的核心方法给上锁

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

格式 

修饰符 synchronized 返回值类型 方法名称 (形参列表) {

            操作共享资源的代码

}

原理

对于实例方法默认使用this作为锁对象     对于静态方法默认使用类名.class对象作为锁对象

3 Lock

为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活,方便

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

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

为什么放在try...finally

因为锁是必须要解开的,否则其他用户也进不来

线程通信(先了解思想)

常用方法

线程池

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

不使用线程池的问题

如果用户每发起一个请求,后台就创建一个新线程处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能

代表线程池的接口  ExecutorService

如何得到线程池对象 

方式1 (常用)

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

ThreadPoolExecutor构造器的创建对象实例

用KTV形象表示  参数一相当于长久工假设3个  参数二好比仅招10个员工,所以还可以找7个临时工,参数5相当于3个正式工都在忙,再来人时需要等待,假设就可以等待5个人,参数六相当于人力部招人的,参数七表示等待已满,再来人时选择 一脚踢出去还是咋样

方式2

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

线程池常见面试题

临时线程什么时候创建?

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

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

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

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

线程池处理Runnalbe任务

此处用第一个execute

线程池处理Callable任务

此处用第二个submit   返回未来任务对象,用Future<V>接收

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

Executors线程池的工具类通过调用方法返回不同类型的线程池对象

Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的

以上第二个为例

仅有3个线程,且为固定的

Executors使用过程中存在的问题导致其在大型系统中无法使用

Timer定时器

Timer定时器的特点和存在的问题

Timer是单线程,处理多个任务按照顺序执行,如果有一个任务有睡眠,导致存在延时与设置定时器的时间有出入

可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行

3000表示的意思是  点开执行任务时,需要等3s才会开始执行

2000表示的意思是  开始执行任务时,需要每2s才会执行一次

ScheduledExecutorService定时器

优点

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

并发与并行(并发 CPU分时轮询的执行进程  并行  同一个时刻在同时执行

正在运行的程序就是一个独立的进程 线程是属于进程的  多个线程其实是并发与并行同时进行的

并发的理解

CPU同时处理线程的数量有限    CPU会轮换位系统的每个线程服务,由于CPU切换速度很快,给我们的感觉这些线程在同时进行,这就是并发

并行的理解

在同一个时刻,同时有多个线程被CPU处理并执行

比如  我给全班人发糖 ,由于我跑的快,感觉全班人都在被我发糖  这就是并发

         当我再叫上几个人,一起发糖,就是并行     CPU的线程数就是并行数

线程的生命周期(丛生到挂掉的各种状态)

线程的状态

线程的状态 也就是线程从生到死的过程,以及中间经历的各种状态及状态转换

网络编程

网络编程可以让程序与网络上的其他设备中的程序进行数据交互

常见的通信模式

Client-Server(CS) 客户端与服务端   需要下载客户端软件

Browser/Server(BS)浏览器与服务端  直接用浏览器即可

实现网络编程关键的三要素

IP地址 设备在网络中的地址

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

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

IP地址形式

公网地址  私有地址(局域网使用)

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

IP地址常用命令

ipconfig  查看本机IP地址

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

特殊IP地址

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

IP地址操作类--InetAddress

常用方法 API

端口号

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

端口类型

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

注册端口 1024-49151 分配给用户进程或某些应用程序(如 Tomcat 占用8080  MySQL占用3306)

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

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

协议(我们程序员更多关注的是应用层)

TCP通信协议的特点是什么样的

它是一种面向连接的可靠通信协议

传输前,采用“三次握手”方式建立连接,点对点的通信,所以可靠

在连接中可进行大数据量的传输

通信效率较低

UDP协议的特点

用户数据报协议

UDP是面向无连接、不可靠传输的通信协议

速度快,有大小限制一次最多发送64k,数据不安全,易丢失数据

UDP协议通信模型

上面是单发单收,下面是实现多发多收

UDP三种通信方式

单播:单台主机与单台主机之间的通信

广播:当前主机与所在网络中的 所有主机通信

组播:当前主机与选定的一组主机的通信

UDP实现广播

使用广播地址  255.255.255.255

具体操作

发送端发送的数据包的目的地写的是广播地址 且指定端口

本机所在网段的其他主机的程序只要匹配端口成功就可以接受消息了

UDP实现组播

使用组播地址:224.0.0.0~239.255.255.255

具体操作

发送端的数据包的目的地是组播IP

接收端必须绑定该组播IP  端口还要对应发送的目的地端口 这样既可接受该组播消息

DatagramSocket的子类MulticastSocket可以在接收端绑定组播IP

TCP通信

模型

客户端(发送端)实现

Socket

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

Socket类成员方法

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

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

1 创建客户端的Socket对象,请求与服务端链接

2 使用Socket对象调用getOutputStream()方法得到字节输出流

3 使用字节输出流完成数据的发送

服务端(接收端)实现

ServerSocket

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

ServerSocket类成员方法

public Socket accept()  等待接收客户端Socket通信连接,连接成功返回Socket对象与客户端建立端到端通信

1 创建ServerSocket对象,注册服务端端口

2 调用ServerSocket对象的accept()方法,等待客户端的连接,并得到Socket管道对象

3 通过Socket对象调用用getIputStream()方法得到字节输入流,完成数据的接收

注意  需要把客户端对象第四步发送消息的那里变为println  或者   在内容加个 \n 因为接收端是读取的一行,当没遇到换行它是不会停止的,但是发送端发完消息后,就结束了,相应的接收端也会挂掉,见下面

没加的

加了的

报错的原因是因为读取完一行没有下一行了

还可以把接收端的while改为if  即只接收一次

使用TCP通信实现  多发多收消息(基于上面的 只展示修改代码的部分)

1 可以使用死循环控制服务端收完消息继续等待下一个消息

2 客户端也可以使用死循环等待用户不断输入消息

3 客户端一旦输入了exit,则关闭客户端程序,并释放资源

现在服务端是单线程的,每次只能处理一个客户端消息(解决见 下面)

多线程处理多个客户端

原理如图,

就是用多线程原理

主线程定义了循环负责接收客户端Socket管道连接

每接收到一个Socket通信管道后分配一个独立的线程负责处理它

定义一个线程

上面这个存在问题,如果线程创建过多会导致服务器崩溃,所以用线程池优化

用来包装成任务的

TCP通信实战案例--即时通信

即时通信是指一个客户端的消息发出去,其他客户端可以接收到

即时通信需要进行端口转发的设计思想

服务端需要把在线的Socket管道存储起来

一旦收到一个消息要推送给其他管道

上面讲的都是CS架构模式

TCP如何实现BS架构(不需要开发客户端)

客户端使用浏览器发起请求

服务端必须按照浏览器的协议规则响应数据

浏览器使用的是HTTP协议

上面两个后期巩固可看P185

单元测试

单元测试就是针对最小的功能单元编写测试代码,Java程序最小的功能单元是方法,因此,

单元测试就是针对Java方法的测试,进而检查方法的正确性

目前测试方法是怎么进行的,存在什么问题

只有一个main方法,如果一个方法的测试失败了,其他方法测试会受到影响

无法得到测试的结果报告,需要程序员自己去观察是否成功

无法实现自动化测试

由此引出了Junit单元测试框架

JUnit单元测试框架

JUnit是使用Java语言实现的单元测试框架,在IDE工具都集成了JUnit,这样就可以直接在IDE中编写并运行JUnit测试

JUnit优点

JUnit可以灵活的选择执行哪些测试方法,可以一键执行全部的测试方法

JUnit可以生成全部方法的测试报告

单元测试中某个方法测试失败了,不会影响其他测试方法的测试

单元测试快速入门

1  将JUnit的jar包导入到项目中(一般IDEA都会给你整合好,但是如果没有整合好的话,需要自己动手)

在模块下面创建library,然后将jar包复制粘贴进去

然后选中这两个,右键,add...

2 编写测试方法:该测试方法必须是公共的无参数无返回值的非静态方法

3在测试方法上使用@Test注解:标注该方法是一个测试方法(如果@Test是红色的,表示还没有导入jar包,此时alt+Enter,导入jar包,这就是所谓的自动导入,但我的不行!!!!!!!!!!!!)

4在测试方法中完成被测试方法的预期正确性测试

5在你要运行的方法里面右键运行

也可以在整个类的类名上运行,运行的是整个类里面的所有测试方法

JUnit常用注解

反射概述

反射是指对于任何一个Class类,在“运行的时候”都可以直接得到这个类全部成分

在运行时,可以直接得到这个类的构造器对象:Constructor

在运行时,可以直接得到这个类的成员变量对象:Field

在运行时,可以直接得到这个类的成员方法对象:Method

这种运行时动态获取信息以及动态调用类中成分的能力称为Java语言的反射机制

反射的关键

反射的第一步都是先得到编译后的Class类对象,然后就可以得到Class的全部成分

1 反射获取类对象

三种方法

方式一:Class c1 = Class.forName("包名+类名");

方式二:Class c2 = 类名.class;

方式三:Class c3 = 对象.getClass();

2 使用反射技术获取构造器对象并使用

反射第一步是先得到类对象,然后从类对象中获取类的成分对象

Class类中用于获取构造器的方法

前两种方法

后两种方法

获取构造器的作用依然是初始化一个对象返回

Constructor类中用于创建对象的方法

如果是非public的构造器,需要打开权限(暴力反射),然后再创建对象

3 反射技术获取成员变量并使用

反射的第一步是先得到类对象,然后从类对象中获取类的成分对象

Class类中用于获取成员变量的方法

获取成员变量的作用依然是在某个对象中取值、赋值

Field类中用于取值 赋值的方法

如果是非public的构造器,需要打开权限(暴力反射),然后再取值、赋值

4 使用反射技术获取方法并使用

反射的第一步是先得到类对象,然后从类对象中获取类的成分对象

Class类中用于获取成员方法的方法

获取成员方法的作用依然是在某个对象中执行此方法

Method类中用于触发执行的方法

获取单个方法的执行情况如下

反射的作用

反射是作用在运行时的技术,此时集合的泛型将不能产生约束了,此时可以为集合存入其他任意类型的元素

泛型只是在编译阶段可以约束集合只能操作某种类型,在编译阶成Class文件进入运行阶段的时候,其真实类型都是ArrayList了,泛型相当于被擦除了

还有一种更简洁的方法让其支持其他类型

反射最重要的还是做Java高级框架

需求:给你任意一个对象,在不清楚对象字段的情况下,可以把对象的字段名称和对应值存储到文件中去

定义如下方法:

在main方法中调用                                                      最终会生成文件

内容如下 

注解

注解的作用

对Java中类、方法、成员变量做标记,然后进行特殊处理

例如:JUnit框架中,标记了注解@Test的方法就可以被当成测试方法执行,而没有标记的就不能当成测试方法执行

自定义注解

特殊属性

value属性,如果只有一个value属性的情况下,使用value属性的时候可以省略value名称不写

但是如果有多个属性,且多个属性没有默认值,那么value名称是不能省的

可以省的情况

不可以省的情况

元注解

注解注解的注解

有两个··

@Target   约束自定义注解只能在哪些地方使用

@Retention  神明注解的生命周期

注解的解析

注解的操作中经常需要解析,注解的解析就是判断是否存在注解,存在注解就解析出内容

与注解相关的接口

案例

注解的应用

模拟JUnit框架

动态代理

代理指某些场景下会找一个代理对象,来辅助自己完成一些工作,如歌星(经纪人),代理主要是对对象的行为额外做一些辅助操作

如何建代理对象

Java中代理的代表类是:java.lang.reflect.Proxy

Proxy提供了一个静态方法,用于为对象产生一个代理对象返回

在Java中实现动态代理的步骤是什么样的

必须存在接口          被代理对象需要实现接口          使用Proxy类提供的方法

通过代理调用方法,执行流程是什么样的

先走向代理 

代理可以为方法额外做一些辅助工作   

开发真正出发对象的方法的执行

回到代理中,由代理负责返回结果给方法的调用者

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值