拓展内容
编程方式
命令式编程:命令“机器”如何去做事情*(how),这样不管你想要的是什么(what)*,它都会按照你的命令实现。比如说面向过程编程
声明式编程:告诉“机器”你想要的是什么(what),让机器想出如何去做*(how)。比如说SQL语句,编程语言中的接口
函数式编程:一切皆是函数,函数可以作为参数作为返回值,详见kotlin的高阶函数
面向切面编程:指对于一段代码我们从中间织入语句,常用的就是为一个函数调用前后插入语句做操作。
在java中这些都可以被从形式上支持
函数式相关
lambda表达式
如何从签名的角度将一个方法与其他方法分离
方法有三个特性表现自己和其余方法的不同
1,调用者,从OOP的角度来看就是这个方法归属于哪个类
2,方法的返回值,即预期返回类型
3,方法形参,即预期的输入类型
另外在java中声明的异常也算是方法的一部分,这个不在讨论范围内
从数学的角度来说函数就是值对值的映射关系,再从OOP的角度来说,如果我声明一个int到Srting的函数,即
int -> String的映射,这只是一种关系,即声明签名。其余符合这个映射关系的函数就可以视为这个声明的实现
即如下伪代码也是成立的
这个就是函数可以如同对象一样传递
java如何书写lambda表达式
首先java lambda表达式比较受限,只支持sam(Single Abstract Method)接口,而且是以对象的形式支持的
其核心就在于形参,方法体和返回值
即()->{ }形式
观察这个接口
首先确定形参 String s,其次确定返回值int
即
若方法体只有单行且返回值为int,简化版本 (s) -> s.length
lambda实质
通过字节码可以看出,调用lambda方法时使用了invokedynamic
,该字节码命令是为了支持动态语言特性而在Java7中新增的。Java的lambda表达式实现上也就借助于invokedynamic
命令。
字节码中每一处含有invokeDynamic指令的位置都称为动态调用点,这条指令的第一个参数不再是代表方法调用符号引用的CONSTANT_Methodref_info常量,而是变成为JDK7新加入的CONSTANT_InvokeDynamic_info常量,从这个新常量中可得到3项信息:引导方法(Bootstrap Method,此方法存放在新增的BootstrapMethods属性中)、方法类型和名称。引导方法是有固定的参数,并且返回值是java.lang.invoke.CallSite对象,这个代表真正要执行的目标方法调用。根据CONSTANT_InvokeDynamic_info常量中提供的信息,虚拟机可以找到并执行引导方法,从而获得一个CallSite对象,最终调用要执行的目标方法。
以上看不懂?没关系
我们看反射结果
以我的项目中一个例子,所以实际上就是生成了一个函数而已
面向切面编程
从代理模式讲起
何为代理模式?即一个代理类包含一个被代理的引用,实际操作还是由被代理的对象操作
代理类可以在被代理的方法执行前后执行一些代码
其伪代码如下
这个就是典型的静态代理,这个在调用前后的执行操作就是对这个函数的切面操作
动态代理
对于大部分情况下我们并不能完全确认是哪个需要被代理,而且代理类和被代理类的代码混合在一起不利于后期维护,有没有一种方法可以解决这个问题呢?
是有的,动态代理,区别于静态代理,其更侧重于运行时将两者代码编织在一起
以面向接口的jdk自带的动态代理代码举例
核心原理就将原类中方法抽象为Method类,以反射的方式调用,这个代理类为运行时字节码生成的实例
除此之外还有以类继承形式实现的动态代理库CGLIB
也可以了解一下编译期静态代理AspectJ
完整解析+源码参考基于jdk动态代理的Aop实现
IO方式
IO分类
阻塞角度:阻塞IO和非阻塞IO这两个概念是程序级别的。主要描述的是程序请求操作系统IO操作后,如果IO资源没有准备好,那么程序该如何处理的问题:前者等待;后者继续执行(挂起)
线程角度:同步IO 和 异步IO,这两个概念是操作系统级别的。主要描述的是操作系统在收到程序请求IO操作后,如果IO资源没有准备好,该如何响应程序的问题:前者不响应,直到IO资源准备好以后;后者返回一个标记(好让程序和自己知道以后的数据往哪里通知),当IO资源准备好以后,再用事件机制返回给程序。
网络IO
同步阻塞模式(Blocking IO)
伪代码如下
也就说若当前数据没有准备好则当前线程会阻塞在这里,即一个线程只能同时处理一次请求
非阻塞IO(Non-Blocking IO)
伪代码实现
非阻塞 IO 的核心在于使用一个 Selector 来管理多个通道,可以是 SocketChannel,也可以是 ServerSocketChannel,将各个通道注册到 Selector 上,指定监听的事件。
之后可以只用一个线程来轮询这个 Selector,看看上面是否有通道是准备好的,当通道准备好可读或可写,然后才去开始真正的读写,这样速度就很快了。我们就完全没有必要给每个通道都起一个线程。
NIO 中 Selector 是对底层操作系统实现的一个抽象,管理通道状态其实都是底层系统实现的,这里简单介绍下在不同系统下的实现。
select:上世纪 80 年代就实现了,它支持注册 FD_SETSIZE(1024) 个 socket,在那个年代肯定是够用的
1.6上windows的实现就是这个
poll:1997 年,出现了 poll 作为 select 的替代者,最大的区别就是,poll 不再限制 socket 数量。
select 和 poll 都有一个共同的问题,必须全遍历一遍才知道哪个准备好了
epoll:2002 年随 Linux 内核 2.5.44 发布,epoll 能直接返回具体的准备好的通道,时间复杂度 O(1)
这个e就是event的意思,对应连接时间,读写事件都直接投递到队列中,只要遍历这个队列就行了,直接处理事件
异步IO(Asynchronous IO)
简单来说就是直接交由内核处理,并且添加一个回调。
即提交任务后线程可以直接返回。
在 Unix/Linux 等系统中,JDK 使用了并发包中的线程池来管理任务,具体可以查看 AsynchronousChannelGroup 的源码。
在 Windows 操作系统中,提供了一个叫做 I/O Completion Ports 的方案,通常简称为 IOCP,操作系统负责管理线程池,其性能非常优异,所以在 Windows 中 JDK 直接采用了 IOCP 的支持,使用系统支持,把更多的操作信息暴露给操作系统,也使得操作系统能够对我们的 IO 进行一定程度的优化。
而对于Linux平台,目前稳定版本上的AIO实现其实就是NIO,这就是为什么要用JUC的线程池管理
文件IO
在同步文件IO中,线程启动一个IO操作然后就立即进入等待状态,直到IO操作完成后才醒来继续执行。而异步文件IO方式中,线程发送一个IO请求到内核,然后继续处理其他的事情,内核完成IO请求后,将会通知线程IO操作完成了
总结
最简单判断哪个模型最节约资源的方法,就是看自己java程序的线程是不是会被挂起,就是实际运行时间占比
如何判断是否是async,一般来说默认是同步,而后缀async或者前缀async或者方法含有回调的一般是异步
即如果我们要做到最大吞吐量(本机性能的顶点)必须尽可能使用户态线程高效运行,减少阻塞时间
Last updated