Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
M
mynote
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ClassmateWang
mynote
Commits
4a43c04f
Commit
4a43c04f
authored
Nov 11, 2021
by
ClassmateWang
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
2021-11-11 15:59 /Netty/黑马/4)NettyApi.md
parent
224f0c00
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
384 additions
and
6 deletions
+384
-6
JAVA/JVM/1)类加载器.md
+80
-0
JAVA/JVM/第二章 Java虚拟机结构.md
+3
-0
Netty/黑马/4)NettyApi.md
+301
-6
No files found.
JAVA/JVM/1)类加载器.md
0 → 100644
View file @
4a43c04f
# 1)类加载器
# 1)类加载器
## 一、定义
`什么是类加载器?`
**Java 编译器**
会将Java
**代码**
转化为
**Java 虚拟机的源指令**
,并将源指令
**存储到.class文件**
中,这里每个类文件都会包含某个类或者接口的定义和实现代码。
而
**Java 类加载器**
会处理这些
**虚拟机指令**
,并将其翻译为
**目标机器的机器语言**
。
## 二、类加载过程
类加载器的分类:
-
引导类加载器
-
加载系统类(往往从rt.jar 文件中进行加载)
-
通常使用C语言来编写,是虚拟机不可分割的一部分
-
没有ClassLoader 对象
-
扩展类加载器
-
从jre/lib/ext 加载标准的扩展jar
-
系统类加载器
-
用于加载应用类
-
从系统环境变量或者-classpath 命令行参数中指定的目录中查找类
在Oracle 的Java 语言实现中,扩展类加载器和系统类加载器都是通过Java来实现的,他们都是URLClassLoader 类的实例
`过程:`
> 注意:虚拟机只加载程序执行时需要的类文件
1.
虚拟机通过文件读取机制从磁盘或者网络加载MyProgram 类文件中的内容
2.
类的解析(加载某个类依赖的所有类):如果MyProgram 类拥有类型为另一个类的域,或者是拥有超类,则这些类文件也会被加载
3.
接着 虚拟机执行MyProgram 中的main 方法
4.
如果mai 方法或者main 调用的方法要用到更多的类,那么接下来就会加载这些类
## 三、类加载器的层次结构
类加载器都有一种父——子的层次结构,
**除了引导类加载器外,每个类都有一个父类加载器**
。且类加载器会为它的父类加载器提供给一个机会,以便加载任何给定的类,
**并且只有其父类加载器加载失败时,它才会执行并加载该类**
。
> 比如说:
>
> 如果要求系统类加载器加载一个系统类(java.util.ArrayList),它首先会要求扩展类加载器去加载,而扩展类加载器会要求引导类加载器去加载。
>
> 此时引导类加载器会找到并加载rt.jar 中的这个类,而不需要其他类加载器做更多的处理。
如果说对应的类被达成了jar包,那么可以直接的通过URLClassLoader 类的实例去加载这些类:
```
java
URL
url
=
new
URL
(
"file://path/to/plugin.jar"
);
URLClassLoader
pluginLoader
=
new
URLClassLoader
(
new
URL
[]{
url
});
Class
<?>
cl
=
pluginLoader
.
loadClass
(
"mypackage.MyClass"
);
```
因为URLClassLoader 中并没有指定父类加载器,所以pluginLoader的父类加载器就是系统类加载器
每个线程都有一个类加载器的引用,称为上下文类加载器,主线程的上下文类加载器是系统类加载器,当新的线程创建时,这些新线程的类加载器就会被设置为创建他们的线程的类加载器,所以如果不做任何操作的话
**所有线程的类加载器都将是系统类加载器**
`设置类加载器:`
```
java
Thread
t
=
Thread
.
currentThread
();
t
.
setContextClassLoader
(
loader
);
```
`获取类加载器:`
```
java
Thread
t
=
Thread
.
currentThread
();
ClassLoader
loader
=
t
.
getContextClassLoader
();
/*通过获取的类加载器加载类*/
Class
cl
=
loader
.
loadClass
(
className
);
```
## 四、类加载器作为命名空间
在同一个虚拟机中其实是允许两个包名和类名都相同的类存在,因为类是由它的类全名和类加载器确定的,这项技术在加载来自多出的代码时非常有用。
可以通过不同的类加载器的实例去加载,就可以避免因为类名重复的情况引发的问题。
**也就是说即使包名类名相同若使用不同的类加载器也能保证不冲突。**
JAVA/JVM/第二章 Java虚拟机结构.md
0 → 100644
View file @
4a43c04f
# Java 虚拟机结构
# Java 虚拟机结构
\ No newline at end of file
Netty/黑马/4)NettyApi.md
View file @
4a43c04f
# 4)N
etty-api
# 4)N
etty-api
...
...
@@ -38,13 +38,15 @@ Netty 在Java 网络应用框架的地位就好比:Spring在JavaEE中的地位
## 二、Hello World
###
2.1 目标需求
###
目标需求:
开发一个简单的服务器端和客户端
-
客户端向服务器发送HelloWorld
-
服务器仅接收不返回,实现单向通信
### 实现:
导入依赖:
```
xml
...
...
@@ -170,9 +172,302 @@ public class HelloClient {
> - handler 理解为数据的处理工序
> - 工序有多道,合在一起就是pipline,pipline 负责发布事件(读、读取完成....),然后传播给每个handler,handler 只对自己感兴趣的事件进行处理(重写了相应事件的处理方法)
> - handler分为
> - Inbound
> - Outbound
> - Inbound
入站(输入时通过入站处理器处理)
> - Outbound
出战
> - pipline 流水线,对数据的处理有的时候一道工序不够有的时候需要多道工序,而这个就像流水线多道工序处理数据
> - 把eventLoop 理解为处理数据的工人
> - 工人可以管理多个Channel 的IO操作,并且一旦一个工人负责了某个channel,就要负责到底
> -
> - 工人可以管理多个Channel 的IO操作,并且一旦一个工人负责了某个channel,就要负责到底(目的是为了线程安全)
> - 工人可以执行IO操作,也可以进行普通的任务处理,每位工人有任务队列,队列里可以堆放多个channel 的待处理任务,任务分为普通任务和定时任务
> - 工人按照pipline 的顺序,依次按照handler 的规划(代码)处理数据,可以为每道工序指定不同的工人
## 三、组件
### 3.1EventLoop
`事件循环对象&事件循环组`
EventLoop 本质上是一个
**单线程执行器**
(同时维护了一个selector),里面的run方法
**处理channel 上源源不断的IO事件**
它的继承关系比较复杂:
-
一条线是继承自j.u.c.ScheduledExecutorService 可以用于执行定时任务,同时也包含了线程池中的所有方法
-
另一条线是继承自netty自己的OrderedEventExecutor,有序的执行任务
-
提供了boolean isEventLoop(Thread thread)判断一个线程是否属于此EventLoop
-
提供了parent 方法看看自己属于哪个EventLoopGroup
`事件循环组:`
EventLoopGroup 就是一组EventLoop,channel 一般会调用EventLoopGroup 的register 方法来绑定其中的一个EventLoop,
**后续这个Channel 上的IO事件都由此EventLoop 来处理**
(保证了IO事件处理时的线程安全)
#### 处理普通事件
> 执行普通任务的意义在于:
>
> - 异步处理
> - 事件分发的时候可以通过这种方式,将接下来一段代码的执行从一个线程传递给另一个线程
```
java
@Slf4j
public
class
TestEventLoop
{
public
static
void
main
(
String
[]
args
)
{
/**
* 1、创建事件循环组
* NioEventLoopGroup 可以用于处理IO事件,定时事件,普通任务
* DefaultEventLoopGroup 只能处理定时事件和普通任务,并不能处理IO事件
*
* 一个事件循环是一个线程来维护的
* 这里传入的参数是创建线程的个数,如果没有传入参数则线程默认数为1
*/
EventLoopGroup
group
=
new
NioEventLoopGroup
(
2
);
/*2、group.next
* 循环获取下一个事件循环对象,这样就保证负载均衡
* 依次将不同的channel 注册到不同eventLoop 上
* 就类似于NIO对于两个worker 的处理
* */
group
.
next
().
submit
(()
->
{
/**
* 将事件提交给某个事件循环对象去处理(交给一个线程去处理)
* 实现一个异步处理
*/
try
{
Thread
.
sleep
(
1000
);
}
catch
(
InterruptedException
e
)
{
e
.
printStackTrace
();
}
log
.
debug
(
"ok"
);
});
log
.
debug
(
"main"
);
}
}
```
![
image-20211110152447645
](
https://wangnotes.oss-cn-beijing.aliyuncs.com/notesimage/image-20211110152447645.png
)
-
group.next
-
获取事件循环对象
-
submit 向事件循环对象提交一个事件,一个事件循环对象通过一个线程来专门进行维护
-
也可以使用execute
#### 处理定时任务
> 使用意义:
>
> - 实现keep-live 的时候可以作为连接的保护而使用
> - 通过另一条线程规律执行某个线程
```
java
/**
* 启动一个定时任务,并以一定的频率来执行
* 1、参数一是一个Runnable 接口类型的任务对象
* 2、参数二是初始延时事件
* 3、参数三是间隔时间
* 4、执行单位
*/
group
.
next
().
scheduleAtFixedRate
(()->{
log
.
info
(
"ok"
);
},
0
,
1
,
TimeUnit
.
SECONDS
);
```
![
image-20211110153213782
](
https://wangnotes.oss-cn-beijing.aliyuncs.com/notesimage/image-20211110153213782.png
)
#### 处理IO事件
```
java
@Slf4j
public
class
EventLoopServer
{
public
static
void
main
(
String
[]
args
)
{
new
ServerBootstrap
()
.
group
(
new
NioEventLoopGroup
())
.
channel
(
NioServerSocketChannel
.
class
)
/*初始化器*/
.
childHandler
(
new
ChannelInitializer
<
NioSocketChannel
>()
{
/**
* 这个方法会在connection 建立后调用
* @param nioSocketChannel 泛型对象
* @throws Exception
*/
@Override
protected
void
initChannel
(
NioSocketChannel
nioSocketChannel
)
throws
Exception
{
/*向流水线中添加处理,这里添加一个自定义的处理器*/
nioSocketChannel
.
pipeline
().
addLast
(
new
ChannelInboundHandlerAdapter
(){
@Override
/*这里表示关注的是读操作
没有StringDecoder的时候
Object msg 是ByteBuf类型的数据*/
public
void
channelRead
(
ChannelHandlerContext
ctx
,
Object
msg
)
throws
Exception
{
ByteBuf
buf
=
(
ByteBuf
)
msg
;
log
.
debug
(
buf
.
toString
(
StandardCharsets
.
UTF_8
));
}
});
}
})
.
bind
(
8080
);
}
}
```
![
image-20211111135551286
](
https://wangnotes.oss-cn-beijing.aliyuncs.com/notesimage/image-20211111135551286.png
)
当服务器和客户端两个channel 建立连接之后,那么服务器会用同一个EventLoop 或者说同一个线程来处理这个客户端,建立一个绑定关系
![
image-20211111135947864
](
https://wangnotes.oss-cn-beijing.aliyuncs.com/notesimage/image-20211111135947864.png
)
但是并不是说有多少个channel 就会建立多少了eventLoop,eventLoop的数量是既定的,所以会将channel 循环的分给eventLoop,以达到负载均衡的效果,其次就是上面说的每一个eventLoop 对一个channel 都会负责到底
#### 分工细化一
-
`创建两个eventLoopGroup 分别管理Boss 和Worker`
-
BossEventLoop 中只会有一个线程因为它唯一去处理ServerSocketChannel,而只会有一个ServerSocketChannel
将eventLoop 的职责划分可以划的更细一些,划分为boss 和worker
.channel 会将ServerSocketChannel 绑定到一个EventLoop上
.childHandler 会将SocketChannel 绑定到参数二的EventLoopGroup 中的EventLoop 上,
**初始化器针对的是已经绑定后的SocketChannel**
```
java
new
ServerBootstrap
()
/**
* 划分为boss 和 worker
* 第一个boss 只负责ServerSocketChannel accept 事件
* 第二个worker 只负责socketChannel 上的读写事件 线程数默认是CPU核心数*2
* 这里并不需要将第一个EventLoopGroup 的线程数设为一
* 因为这里的serverSocketChannel 只会和第一个EventLoopGroup 中的一个进行绑定
* 而并没有多个ServerSocketChannel
* 也只会占用一个线程
*/
.
group
(
new
NioEventLoopGroup
(),
new
NioEventLoopGroup
())
.
channel
(
NioServerSocketChannel
.
class
)
```
#### 分工细化二
-
再创建一个EventLoopGroup 去处理耗时较长的操作,而不是用NioEventLoop (worker Thread) 去处理耗时较长的操作
-
避免繁杂逻辑操作影响io线程
-
当需要将两个自定义的handler 建立连接时需要在第一个handler 中调用context.fireChannelRead(msg) 将数据传递给下一个handler,这样才能将两个handler 串起来
-
这里在第一个handler 可以
**实现条件判断看看是不是要走第二个handler**
,调用了那句代码才会走到对应的handler 中
-
用指定的group 处理特殊情况的操作,两个用的线程是不同的,因为eventLoop 中线程的数量是有限的,而worker 线程要服务很多channel ,所以如果一个线程做比较消耗资源的操作就会降低其他channel 对资源的利用率,所以这里才会创建一个新的group 其实就是新的线程去处理对应的特殊操作
-
eventLoop 就是
**一个线程且带着一个selector**
```
java
public
static
void
main
(
String
[]
args
)
{
/*创建处理特殊事件的Group*/
EventLoopGroup
group
=
new
DefaultEventLoop
();
new
ServerBootstrap
()
.
group
(
new
NioEventLoopGroup
(),
new
NioEventLoopGroup
())
.
channel
(
NioServerSocketChannel
.
class
)
.
childHandler
(
new
ChannelInitializer
<
NioSocketChannel
>()
{
@Override
protected
void
initChannel
(
NioSocketChannel
nioSocketChannel
)
throws
Exception
{
nioSocketChannel
.
pipeline
().
addLast
(
new
ChannelInboundHandlerAdapter
(){
@Override
public
void
channelRead
(
ChannelHandlerContext
ctx
,
Object
msg
)
throws
Exception
{
ByteBuf
buf
=
(
ByteBuf
)
msg
;
log
.
debug
(
buf
.
toString
(
StandardCharsets
.
UTF_8
));
/*将消息传递给下一个handler*/
ctx
.
fireChannelRead
(
msg
);
}
}).
addLast
(
group
,
"handler2"
,
new
ChannelInboundHandlerAdapter
(){
@Override
public
void
channelRead
(
ChannelHandlerContext
ctx
,
Object
msg
)
throws
Exception
{
super
.
channelRead
(
ctx
,
msg
);
}
});
}
})
.
bind
(
8080
);
}
```
#### 切换线程
`在流水线执行过程中,handler 执行是如何切换EventLoop的,例如:如何从NioEventLoop 切换到DefaultEventLoop 上执行的(换人)`
![
image-20211111144854012
](
https://wangnotes.oss-cn-beijing.aliyuncs.com/notesimage/image-20211111144854012.png
)
> 如果两个handler 绑定的是同一个线程(EventLoop)就可以直接调用,**如果绑定的是不同线程,而是会将要调用的代码封装为一个Runnable 对象中然后传递给下一个handler的线程去处理**
### 3.2Channel
channel 的主要作用:
-
close()
-
用于关闭channel
-
closeFuture()
-
用于处理channel 的关闭,close 之后的善后处理
-
sync方法 是同步等待channel 的关闭
-
用addListener 方法是异步等待channel 的关闭
-
pipline()
-
添加处理器
-
write()
-
将数据写入
-
由于netty 的缓冲机制所以写入channel 后不会立马发出
-
需要达到一定数据量
-
**需要执行flush 方法**
-
writeAndFlush()
-
将数据写入并刷出
-
**保证数据写入channel 后立马发出**
#### channelFuture
`用于处理建立连接后的结果(同步等待还是异步执行)`
带future 或者promise 的类型都是和异步方法进行使用的
> 客户端中 connect 方法的返回值是一个ChannelFuture 对象,如果不调用channelFuture.sync()的话后面的代码就无法正常执行:
分析:
```
java
/*connect 是异步非阻塞方法,main方法只是调用,真正执行connect的是NIO线程*/
.
connect
(
new
InetSocketAddress
(
"localhost"
,
8080
));
// future.sync();
Channel
channel
=
future
.
channel
();
channel
.
writeAndFlush
(
"Hello,world"
);
```
connect
**是异步非阻塞**
,main线程调用之后会立即返回,如果此时没有调用sync 方法会继续向下执行,下面的channel 对象会建立成功,但是实际上因为连接
**并没有建立成功所以没有爆空指针错误**
,而是代码执行无效。
主线程其实并不直到channel 下的连接是否建立好,因为建立连接的是NIO线程而不是主线程
这里connection 的建立需要一些事件,
**必须阻塞直到连接建立成功,再去继续执行才能执行成功。**
**<u>正确处理:</u>**
-
channelFuture.sync()
-
谁发起的调用谁来对建立好的连接进行后续操作
-
可以通过使用sync方法来同步结果
-
阻塞住当前线程(调用sync方法的线程),直到nio线程连接建立完毕
-
相当于主线程主动的去等待,实际执行针对连接建立后对象操作的还是主线程
-
channelFuture.addListener(回调对象) 异步处理结果
-
通过回调对象处理,将连接建立后的对象操作扔给nio 线程,由NIO线程去处理
-
NIO线程建立好了,就会主动去调用operationComplete 方法,
```
java
//// future.sync();
// Channel channel = future.channel();
// channel.writeAndFlush("Hello,world");
future
.
addListener
(
new
ChannelFutureListener
()
{
@Override
public
void
operationComplete
(
ChannelFuture
channelFuture
)
throws
Exception
{
Channel
channel
=
channelFuture
.
channel
();
channel
.
writeAndFlush
(
"Hello,world"
);
}
});
```
处理建立连接完成之后操作的是NIO线程而不是主线程(处理连接建立的是netty 自己配置完成的NIO线程)
### 3.3Future&Promise
### 3.4Handler&Pipeline
### 3.5ByteBuf
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment