Java
JAVA 的8中数据类型
- 1个字节 = 8 位
- 6种数字类型 byte 1字节,short 2 , int 4, long 8, float 4, double 8
- char 2字节=16位
- boolean 1位
- 在使用long时 要在数值后加L 否则当作整型解析
String
- stirng 不可变 优点:可缓存hash值 2、string pool的需要 3、安全性 4、线程安全
- stringbuffer 和 stringbuilder 可变
- stringbuffer(利用了synchronized同步)和string 线程安全, stringbuilder线程不安全
重写和重载
- 重写为了实现父类与子类之间的多态性 在运行时起作者用 对父类的方法进行重写 所有参数都相同
- 重载 是一个类中多态性的表现 在编译时起作用 让类以统一的方式 处理不同类型的数据 参数可以不同
装箱与拆箱
- 装箱: 将基本数据类型 用它们对应的应用类型 包装起来 valueOf(int)
- 拆箱: 将包装类型 转化为 基本数据类型 intValue()
- 数值[-128,127] 之间的数存在常量池中
线程的启动方式
- 继承Thread类实现多线程(Java不支持多重继承 必须单继承 因此重写接口更方便,继承整个Thread类开销过大)
- 重写Runnabe接口的run方法 创建实现这个接口的实例 再用这个实例 创建一个Thread 实例 调用Thread实例的start方法 启动线程
- 重写Callable接口的call方法 可以有返回值 通过FutrueTask进行封装
- 通过线程池启动多线程
run 和 start的区别
- 不要直接调用Thread类或Runnable对象的run方法,直接调用run方法只会在同一个线程中执行任务,而没有启动新的线程。而使用start方法时会创建执行run方法的新线程。
Runable
- Runnable是一个函数式接口,可用lambda表达式创建实例, 必须覆盖run方法
- Runnable r = ()->{ task code} // 覆盖run方法
- var t = new Thread(r)
- t.start()
线程的6种状态
- NEW 新建: 用new 创建新线程时
- Runaable 可运行: 调用start方法时,此时可能在运行也可能没有运行,yield() 方法可以将正在执行的线程向另外一个线程交出运行权,抢占式调度系统给每个可运行线程一个时间片来执行任务;多处理器时,线程数多于处理器数时,调度器还是需要分配时间片
- Blocked 阻塞: 线程试图获取内部对象锁,但锁被其他线程占有时进入阻塞。其他线程释放锁且调度器允许本线程加锁,变成非阻塞状态。
- Waiting 等待:当前线程等待另一个线程通知调度器出现一个条件
- Timed Waiting 计时等待:调用具有超时参数的方法,会让线程进入计时等待,保持到计时期满或接收到适当的通知。
- Terminated 终止: 2个原因 run方法正常退出,线程自然终止;一个没有被捕获的异常终止run方法,线程意外终止
一些线程的操作
- Thread.yield() 运行变为就绪
- Thread.join() 等待指定的线程终止
- Thead.State getState() 可以获取到线程的6种状态
- setName()设置线程名
线程的属性
中断线程
-
对线程调用interrupt 方法设置线程的 中断状态 为True
-
检查当前线程是否设置中断状态 Thread.currentThread().isInterrupated()
-
线程被阻塞时无法检查中断状态,会抛出InterruptedException
-
void interrupt() 向线程发起中断,中断状态为true,此时调用sleep,会抛出InterruptedException
-
static boolen interrupted() 测试当前线程是否被中断,并设置当前线程的中断状态为false
-
boolean isInterrupted() 测试当前线程是否被中断 但不改变中断状态
-
使用thread.interrupt()中断线程的两种方式:
1、 线程中的run方法调用 sleep 等阻塞方法,此时调用interrupt 会抛出InterruptedException导提前结束线程
2、 若不执行sleep,且run方法执行一个无限循环,可以通过调用interrupted()方法检查是否被中断
shutdown() 和 shutdownNow()
- 线程执行完毕之后关闭
- shutdownNow 调用每个线程的interrupt() 方法
守护线程
- t.setDaemon(true) 将一个线程设为守护线程或用户线程,为其他线程服务 例如计时器 清空过时缓存,在线程启动之前调用
- 只剩下守护线程时 虚拟机会退出
12.4 同步
- 竞态条件 race condition :一段程序被多个线程执行,线程执行的先后顺序不一致会导致最终的状态(结果)不同,我们就称这段代码有竟态条件
12.4.3 锁对象
- 基本形式 重入锁 ReentrantLock 线程可以反复获得已拥有的锁,并更改持有计数hold count
private var bankLock=new ReentrantLock();
fun
{
bankLock.lock();
try{do something}
finally{banLock.unLock();}
}
- unlock一定要在finally子句中,否则若临界区代码抛出异常,锁不释放,则其他线程永远阻塞
12.4.4 条件对象
- 线程进入临界区后却发现只有满足某个条件之后才能执行,使用条件对象管理已经获得锁 却不能做有用工作的线程
- 使用方法 sufficientFunds=bankLock.newCondition()获得锁的条件对象
transfor()
{
bankLock.lock()
try
{
//注意是while
while(accounts[from]<amount) sufficientFunds.await();// 不满足条件线程等待 并放弃锁
sufficientFuns.signalAll();// 通知等待的线程 可能满足条件 值得再次检查
}
finally
{
bankLock.unlock();
}
}
12.4.5 synchronized关键字
锁总结:
- 锁用来保护代码片段,一次只能有一个线程执行被保护的代码
- 锁可以管理试图进入被保护代码段的线程
- 一个锁可以有多个相关联的条件对象
- 每个条件对象管理已经进入临界区但不能运行的线程
synchronized关键字作用:
-
对方法加synchronized关键字, 方法就使用了内部对象锁
-
还可以使用内部对象锁的 关联条件 ,但只有一个关联条件
-
此时关联条件通过 wait() 和 notifyAll() 调用 等价于
-
intrinsicCondition.await() intrinsicConditon.signalAll()
-
使用 synchronized(this) 作用于同一个对象
-
使用 synchronized同步一个方法 作用于同一个对象
-
使用 synchronized同步一个类 作用于整个类
-
使用 synchronized同步一个静态方法 作用于整个类
12.4.8 volatile作用
共享变量只进行赋值操作则可以使用volatile进行同步: 保证变量可见性 防止指令重排序 不保证原子性
volatile关键字为实例字段的同步提供了免锁机制,编译器和虚拟机就知道该字段可能被另外一个线程并发更新
每次都到主存读取,保证变量可见性,防止指令重排序
但其不保证原子性
- 禁止JVM指令重排序
- new Instance() 其实分三步
- 1、为对象分配内存空间
- 2、初始化对象
- 3、将对象指向分配的内存
- 在多线程环境下 如果执行顺序为1 3 2,那么如果在执行完第3步时就去访问这个对象,这个对象不为空,但未进行初始化
- 由于其不能保证原子性,如果需要不加锁使用他的话,修改其值的操作必须是原子性的
内存可见性原理
- Lock前缀指令引起处理器缓存写回到内存
- 一个处理器的缓存写回到内存会导致其他处理器的缓存无效
- 因此vlatiole变量的赋值操作会使得修改会直接修改内存中的数据,读取时也直接读取
伪共享(false sharing)
- CPU缓冲行: 缓存以缓存行为单位,通常一个缓存行是64字节
- 伪共享: 当多线程修改互相独立的变量时,如果这些互相独立的变量共享同一个缓存行,当独立的变量修改时,就会无意间使得其他缓存的变量失效,必须再取主内存中重新读取,无意间影响了彼此的性能,这就是伪共享
- 解决方法: 字节填充,在可能发生伪共享的变量后追加字节,使得其独占一个缓冲行,避免伪共享。
- 实例: 如Disruotor 就补全ring buffer 的序列号 避免和其他变量存在一个缓冲行
- https://blog.csdn.net/qq_28119741/article/details/102815659
- http://ifeve.com/disruptor-cacheline-padding/
线程池
创建线程代价大,线程池可用有效的利用有限的线程来启动任务
意义: 线程池提供了一种限制和管理资源,且维护一些基本统计信息,比如已经完成的任务的数量。
优点:
1、 降低资源消耗: 通过重复利用已经创建的线程 降低线程创建和销毁的消耗
2、 提高响应速度:任务到达时,不需要等到线程创建就能立即执行
3、 提高线程可管理性: 线程池堆线程统一分配 调优 和监控
线程池的核心运行机制
-
检测线程池运行状态 若不是RUNNING 则直接拒绝
-
当workerCount<corePoolSize 创建并启动一个线程执行新提交的任务
-
若workerCount>=corePoolSize 且阻塞队列未满 则将任务添加至阻塞队列
-
若阻塞队列已满 且corePoolSize<=workerCount<maximumPoolSize 则创建一个线程来执行新提交的任务
-
若corePoolSize>=maximumPoolSize 则根据拒绝策略处理该任务
-
一般不使用Executors创建线程池
弊端:
FixedThreadPool 和 SingleThreadExecutor: 可能堆积大量请求,导致OOM(Out of memory)
CachedThreadPool 和 ScheduledThreadPool: 可能创建大量线程 导致OOM
ThreadPoolExecutor 自定义线程池 7个参数
- corePoolSize: 核心线程数 最小可以同时运行的线程数量
- maximumPooLSize 最大线程数 当队列存满时,当前可以同时运行的线程数 变为最大线程数
- workQueue 队列,新任务来时先判断当前运行的线程是否达到核心线程数,达到后会存入此队列
- keepAliveTime 等待时间 当线程池数量大于corePoolSize的时候 若无新任务提交,则等待keepAliveTime后销毁多余的线程
- unit: keepAliveTime 的时间单位
- threadFactory: executor 创建新线程时使用(可以在此指定创建新线程的名字)
- handler 饱和策略
饱和策略
- ThreadPoolExecutor.AbortPolicy : 抛出RejectedExecutionException 拒绝新任务(默认)
- CallerRunsPolicy : 不丢弃任何一个请求,执行自己的线程运行任务,且会增加队列容量,但会影响程序整体性能
- DiscardPolicy 不处理新任务 直接丢弃掉
- DiscardOldestPolicy:丢弃最早未处理的任务请求
CountDownLatch 倒计时器 (1初始化cnt; 2conuntDown():cnt--; 3if cnt==0 : 唤醒调用await的方法)
- 协调多个线程之间的同步,用于控制线程等待,让某一线程等待到计时器结束后开始执行
- 可以用来控制cnt个线程阻塞在一个地方,直至cnt个线程都执行完毕
- CountDownLatch countDownLatch= new CountDownLatch(cnt)
- 维护一个计数器cnt ,每次调用countDown() 会让计数器减1 减到0的时候 调用await() 方法的线程就会被唤醒
CyclicBarrier 循环栅栏(1初始化parties;2await():parties--;3 if parties==0: 唤醒调用await的方法并执行barrierAction)
- 与CountDownLatch类似, 实现线程间的等待,
- 线程执行await时 计数器减1并等待 当计数器未0时 所有调用await的方法可以继续执行
- CyclicBarrier(int parties, Runnable barrierAction)
- parties 为计数器初始值 barrierAction 为所有线程都到达屏障时要执行的任务
- 循环栅栏可以通过reset 循环使用
Semaphore 信号量
- 控制多个线程同时访问某资源,synchronized、 ReentrantLock都是一次只能一个线程访问某资源
- acquire() 获得信号量 ;release()释放信号量 ;availabelPermits() 可用信号量数量
- new Semaphore(cnt)
BlockingQueue 阻塞队列
- FIFO 队列: LinkedBlockingQueue , ArrayBlockingQueue(固定长度)
- 优先级队列:PriorityBlockingQueue
- take() 队列为空 阻塞至队列有内容
- put() 若队列满 则阻塞至队列有空闲位置
可见性
- 当一个线程修改了共享变量的值,其他线程能够立即得知这个更改
- 通过变量修改后将新值同步回主存,变量读取前从主存刷新变量值 可实现可见性
- 三种方式:
- volatile
- synchronized
- final 被 final 关键字修饰的字段在构造器中一旦初始化完成,并且没有发生 this 逃逸(其它线程通过 this 引用访问到初始化了一半的对象),那么其它线程就能看见 final 字段的值
execute() 与 submit()的区别
- execute() 提交不需要返回值的任务,无法判断线程池执行成功与否
- submit() 提交需要返回值的任务,返回Future类型的对象 可以判断是否执行成功 get可以获取返回值
锁 lock syn
- 乐观锁 认为读多写少 并发写的可能性低 提高吞吐量 读时不上锁 提交更新时 通过对比版本号(或者时间戳) 检查是否违反数据完整性 避免冲突 java的atomic包 通过CAS(Compare and Swap)
- 悲观锁 写多读少 并发写可能性高 读写数据都加锁 例如java的synchronized
CAS (Compare and Swap)
- 四个参数: Object obj, long valueOffset, Object expect, Object update
- obj 和 valueOffset:表示这个共享变量的内存地址。这个共享变量是obj对象的一个成员属性,valueOffset表示这个共享变量在obj类中的内存偏移量。所以通过这两个参数就可以直接在内存中修改和读取共享变量值。
- expect: 表示预期原来的值。
- update: 表示更新的值。
- 若 共享变量中的值 与 expect相等,则将共享变量中的值更新为update
- 否则 不做任何操作 返回false
hashmap concurrenthashmap 1
- concurrenthashmap 是线程安全的 也就是访问的时候 有值改变时 会报错
- hashmap 是线程不安全的
concurrenthashmap实现原理:
- 1.7 的时候使用分段锁 对桶进行分段,每次只锁一段的数据,多线程访问不同段的数据,不存在锁竞争,提高并发访问量
- 1.8 使用优化的synchronized 和CAS 操作维护并发 一次只锁一个链表 或一棵 红黑树
Hashmap不安全的原因
- 1.7 死循环 数据丢失 :某线程进行扩容时被挂起 同时其他线程已经完成扩容(数据迁移) ,被挂起线程重新执行扩容时会造成 数据丢失和死循环。
- 1.8 数据覆盖 两个线程同时插入时,插入的下标是相同的。一个线程A被挂起,另一个线程B顺利插入,挂起的线程A继续插入之后会覆盖掉刚刚B插入的数据
hashmap 原理
- hashmap的哈希函数是通过移位操作完成
- hashmap的基本结构是链表散列 数组 加 链表(单,头插)
- 当数组长度大于64且链表长度大于8 链表(on)会变为红黑树(logn) 自平衡的排序二叉树
- 默认数组长度是16 接着扩容到32,64;默认认为数组长度不够 先扩容
- 树节点需要占用的空间是普通链表节点的2倍,当包含足够多的节点时才需要转称红黑树
- 当桶内节点小于6个的时候,为节省空间,红黑树转为普通链表
- hash函数位运算 高位与低位混合起来 降低冲突概率 移位异或,比除留余数法性能高 不需要在十进制下运算,直接在二进制下进行移位异或运算
h ^ (h >>> 7) ^ (h >>> 4)
arraylist linkedlist
- 线程安全方面: 都不能保证线程安全
Collections.synchronizedList(new ArrayList()) 可以实现线程安全 - 实现方面: arraylist 用的是object数组 linkedlist 用的双向链表
- linklist 查找on 插入删除o1 插入删除性能好
- arraylist 任意位置插入on 查找o1 查找性能好
- 随机访问:arraylist支持
- 内存占用:arraylist 会在结尾留一定的容量 linkedlist 要保存前驱后继
JAVA对象的创建过程 5步
- 类加载检查:检查是否能从常量池定位到这个类的符号引用,检查这个符号引用代表的类是否 已被加载过 解析和初始化过 否则执行类加载过程
- 分配内存:将一块确定大小的内存从Java堆中划分出来 :方法 1指针碰撞 和 2空闲链表
- 初始化0: 将分配到的 除对象头外的内存空间都初始化为零
- 设置对象头:对象属于的类,如何找到元数据的信息,对象的哈希码,对象的GC分代年龄
- 执行初始化方法
类加载过程:5个阶段
加载、验证、准备、解析、初始化
- 加载:
- 通过类的 完全限定名称 获取定义该类的二进制字节流
- 将该字节流表示的静态存储结构 转化为 方法区 运行时存储结构
- 在内存中生成一个代表该类的Class对象 作为方法区中各类数据的入口
- 验证:确保字节流中包含的信息符合当前虚拟机的要求,且不会危害虚拟机的安全
- 准备: 为类变量分配内存并设置初值,类变量使用的是方法区的内存,一般初始化为0(static),当其为常量时(static final),初始化为给定的值
- 解析: 将常量池的符号引用替换为直接引用的过程
- 初始化: 根据程序员制定的主观计划(写的代码) 初始化类变量和其他资源
类加载器 和 双亲委派
-
类加载器的类别:
- 启动类加载器(Bootsrap Classloader) 加载<JRE_HOME>lib 或 -Xbootclasspath 路径中 虚拟机识别的类库 到虚拟机内存
- 拓展类加载器(Extension ClassLoader) 将<JAVA_HOME>/lib/ext 或 被java.ext.dir 所指定的所有类库加载到内存,开发者可直接使用拓展类加载器ExtClassLoader
- 应用程序类加载器(Application ClassLoader),也称为系统类加载器。 加载用户路径ClassPath上所指定的类库。是ClassLoader中getSystenmClassLoader()的返回值,可直接使用,若未定义,则使用默认的类加载器
-
双亲委派
- 类加载器首先将类加载请求转发到父类加载器,当父类加载器无法完成时才尝试自己加载
- 优点:使得Java类和类加载器具有优先级层次关系,使得基础类得到统一。例如:自己实现的Object类加载时 由于双亲委派模型的存在,加载时会将请求转发到父类加载器,而启动类加载器会先加载 系统的Object类,程序使用的时候 所有的Object也都是系统实现的Object类
- 实现: 先检查是否已经加载过,未加载则让父类加载器加载,当父类加载器加载失败时,再尝试自己加载
- 类加载器通过组合实现父子关系
- 自定义的类加载器 通过基础java.lang.ClassLoader 实现 loadClass() 实现双亲委派模型逻辑,自定义加载器需要重写findClass()方法
JVM 6种内存
- 运行时数据区域:线程私有、线程共享
- 线程私有:
- 程序计数器:记录正在执行的虚拟机字节码指令地址
- 通过改变程序计数器来依次读取指令,实现流程控制
- 多线程时记录当前线程的执行位置,线程再次获得时间片时可知道上次线程运行到了哪里
- 不会出现OOM
- 虚拟机栈
- JAVA方法在执行的同时会创建一个栈帧用于存储 局部变量表、操作数栈、常量池引用等
- 线程请求的栈深度超过最大值会StackOverflowError
- 栈进行动态拓展时无法申请到足够内存会OOM
- 本地方法栈
- 与虚拟机栈类似,为本地方法服务(本地方法一般由其他语言编写 C 、C++ 汇编)
- 为Native方法服务
- 也会出现StackOverflowError和OOM
- 程序计数器:记录正在执行的虚拟机字节码指令地址
- 线程共享:
- 堆
- 对象分配在堆里,是垃圾回收的主要区域
- 方法区
- 存 已经被加载的类信息、常量、静态变量、即时编译器编译后的代码等
- JDK 1.6时 使用永久代实现,存放在虚拟机内存中
- JDK 1.8 开始 方法区移动到元空间 位于本地内存 元空间存储类的元信息,而静态变量和常量池放入堆中
- 直接内存
- 不是虚拟机运行时数据区的一部分,NIO可以只用Native函数库直接分配堆外内存,通过JAVA堆中的DirectByteBuffer对象作为这块内存的引用进行操作,避免了在堆内和堆外内存中来回拷贝数据,显著提高了性能
- 不是虚拟机运行时数据区的一部分,NIO可以只用Native函数库直接分配堆外内存,通过JAVA堆中的DirectByteBuffer对象作为这块内存的引用进行操作,避免了在堆内和堆外内存中来回拷贝数据,显著提高了性能
- 堆
垃圾回收
判断对象是否可回收
- 引用计数法 通过一个对象的引用次数 来判断一个对象是否可以回收
- 可达分析法 为了解决循环引用 AB互相引用 通过一系列GC roots 作为起点进行搜索 若某对象与GC roots没有可达路径 则该对象不可达,不可达对象至少要标记两次才能确定其是否可回收
- GC Roots 一般包含以下内容:
- 虚拟机栈中的局部变量表中引用的对象
- 本地方法栈中JNI中引用的对象(Java Native Interface Java调用C++的规范)
- 方法区中的静态属性引用的对象
- 方法区中常量引用的对象
- GC Roots 一般包含以下内容:
- 方法区的回收
- 主要是对常量池的回收和类卸载
- 类卸载条件:1、堆中不存在该类的任何实例、2、该类的ClassLoader已经被回收 3、该类的Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法
四种引用类型(强 软 弱 虚)
- 强引用:被强引用关联的对象不会被回收 Object obj = new Object()
- 软引用: 内存不够的情况下会被回收
- Object obj = new Object()
- SoftReference
- obj=null
- 弱引用:一定会被回收,只能存活到下次回收发生前 WeakReference
- 虚引用: 不影响 对象生存时间,也无法通过虚引用得到一个对象,只是在对象被回收时可以得到一个系统通知 PhantomReference
垃圾回收算法
1、标记清除(老年代)
- 标记阶段:标记所有被引用的对象
- 清除阶段:回收未标记的对象,并确认与前一个分块是否连续,连续则合并。回收时会将分块连接到“空闲链表”的单向链表
- 分配: 搜索空闲链表中空间大于等于新对象大小size的块,若块大于size,则分割成size和block-size两部分,剩余部分返回空闲链表
- 缺点: 效率低,产生不连续内存碎片,导致无法给大对象分配内存
- 使用空闲链表进行内存分配
2、标记整理 (老年代)
- 存活的对象都向一端移动,并清除掉边界以外的内存
- 缺点: 移动大量对象,效率低 优点:不会产生碎片
- 使用 指针碰撞进行内存分配
3、复制 (新生代)
- 将内存划为大小相等的两块,每次只使用一块,当此块用完后,将当前存活的对象复制到另外一块上
- 缺点: 只使用了内存的一半
- 目前使用此中方法回收 新生代: 划分一块较大的Eden和两块较小的Survivor,空出一块Survivor 当其他2块满时 复制到 这1块上
- Eden:Survivor :Survivor= 8:1:1 当由多于内存10%的对象存话,则利用老年代空间存储放不下的对象
4、 分代收集
- 新生代: 复制算法
- 老年代: 标记清除 或 标记整理
七种垃圾回收器
- Serial ParNew ParallelScavenge CMS SerialOld ParallelOld G1
内存分配策略
- 优先在新生代 Eden上分配
- 大对象直接进入老年代 大小阈值PretenureSizeThreshold
- 长期存活的对象进入老年代 年龄阈值 MaxTenuringThreshold
- 动态对象年龄判定: 若Survivor中相同年龄的所有对象大小超过其空间的一半,那么这个年龄的对象都可以直接进入老年代
- 空间分配担保
在发生 Minor GC 之前,虚拟机先检查老年代最大可用的连续空间是否大于新生代所有对象总空间,如果条件成立的话,那么 Minor GC 可以确认是安全的。如果不成立的话虚拟机会查看 HandlePromotionFailure 的值是否允许担保失败,如果允许那么就会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试着进行一次 Minor GC;如果小于,或者 HandlePromotionFailure 的值不允许冒险,那么就要进行一次 Full GC。
内存回收策略
- Minor GC: 回收新生代,频繁执行,新生代对象存活时间短
- Full GC: 回收老年代和新生代,老年代存活时间长,FULL GC很少执行 速度比Minor GC慢
- 触发条件:
- 调用System.gc() 仅仅建议FULL GC 但不一定真正执行
- 老年代空间不足
- 空间分配担保失败:复制算法需要老年代空间担保,若担保失败则FULL GC
- JDK1.7 之前的 永久代空间不足
- Concurrent Mode Failure
NIO
- NIO 与普通I/O 的区别: NIO是非阻塞的,NIO面向块、I/O 面向流
- 面向流的I/O 一次处理一个字节数据,面向块的NIO一次处理一个数据块,处理数据更快
- 使用通道Channel代替了InputStream和OutputStream流,流只能单向流动读或者写,而通道是双向的,可同时读写
- 通道类型:
- FileChannel 从文件中读写数据
- DatagraChannel 从UDP读写网络种的数据
- SocketChannel: 通过TCP读写网络中的数据
- ServerSocketChannel: 监听TCP连接,堆每个新来的TCP都回创建一个SocketChannel
- 缓冲区:不直接对通道进行读写数据,而是要先经过缓冲区
- 缓冲区类型: 七种 ByteBuffer Char Short Int Long Float Double-Buffer
- 缓冲区状态变量: capacity 最大容量 position当前已经读写的字节数 limit还可以读写的字节数
- 初始时limit capacity都为buffer最大容量。写入缓冲区时positon向前;写入到Channel 调用flip方法,limit置为positon位置, position置0,开始写入的Channel;写完后position和limit分别置0和最大容量
选择器 Selector
- 可将ServerSocketChannel配置为非阻塞,一个线程使用一个选择器Selector 通过轮询的方式监听多个Chnanle的事件,若当前Channel的IO事件未到达则可用轮询其他Channel,不会进入阻塞状态一直等待,一个线程就能处理多个事件,对IO密集型应用具有很好的性能
- 创建选择器 :Selector selector = Selector.open();
- 将通道注册到选择器上:
- ServerSocketChannel ssChannel = ServerSocketChannel.open();
- ssChannel.configureBlocking(false);// 配置为非阻塞
- ssChannel.register(selector, SelectionKey.OP_ACCEPT);
- 事件类型:
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
while (true) {
int num = selector.select();// 监听事件
Set<SelectionKey> keys = selector.selectedKeys();//获取到达的事件
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
// ...
} else if (key.isReadable()) {
// ...
}
keyIterator.remove();
}}
反射
- 反射赋予了我们在运行时分析类以及执行类中方法的能力。通过反射你可以获取任意一个类的所有属性和方法,还可以调用这些方法和属性
- 优点 : 可以让代码更加灵活、为各种框架提供开箱即用的功能提供了便利
- 缺点 :让我们在运行时有了分析操作类的能力,这同样也增加了安全问题。比如可以无视泛型参数的安全检查(泛型参数的安全检查发生在编译时)。另外,反射的性能也要稍差点,不过,对于框架来说实际是影响不大的
- 获取Class对象的四种方法
- 知道具体类的情况下可以使用:Class alunbarClass = TargetObject.class; # 未进行初始化
- 通过 Class.forName()传入类的路径获取:Class alunbarClass1 = Class.forName("cn.javaguide.TargetObject");
- 通过对象实例instance.getClass()获取:TargetObject o = new TargetObject();Class alunbarClass2 = o.getClass();
- 类加载器xxxClassLoader.loadClass()传入类路径获取:Class clazz = ClassLoader.loadClass("cn.javaguide.TargetObject");# 未进行初始化
- Method[] methods = tagetClass.getDeclaredMethods(); 获取类中所有的方法
- Method publicMethod = tagetClass.getDeclaredMethod("publicMethod",String.class);
- publicMethod.invoke(targetObject, "JavaGuide");#获取指定方法并执行
LRU
- Least Recently Used 最近最少使用 用于内存淘汰策略
- 将数据按使用时间排序
- Java实现思路:双向链表 方便任意位置删除 表尾添加;将链表按访问顺序排序; 数量超过阈值后 删除Least Recently Used数据
- LinkedHashMap
- 底层使用双向链表 查找可以使用HashMap的特点
- 构造方法的 accessOrder选项开启后 get操作会保证链表按访问顺序 逆序排列
- 开启accessOrder后,LinkedHashMap实现的afterNodeAccess 会将最近使用或插入的数据放在表尾
- put方法中调用 newNode newTreeNode方法会将Node放在链表末尾
- 此外 LinkedHashMap 还提供了一个Hook方法,removeEldestEntry,这个方法返回True时 会删除表头节点
- 实现方法:继承LinkedHashMap ;accessOrde设为True ;设置阈值 超过阈值 removeEldestEntry 返回true
// 继承LinkedHashMap
public class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int MAX_CACHE_SIZE;
public LRUCache(int cacheSize) {
// 使用构造方法 public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder)
// initialCapacity 初始容量、loadFactor 加载因子默认为0.75 数组容量达到当前容量的75%时 进行孔融
// 默认负载因子(0.75)在时间和空间成本上提供了很好的折衷。较高的值会降低空间开销,但提高查找成本(体现在大多数的HashMap类的操作,包括get和put)
// accessOrder要设置为true,按访问排序
super((int) Math.ceil(cacheSize / 0.75) + 1, 0.75f, true);
MAX_CACHE_SIZE = cacheSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
// 超过阈值时返回true,进行LRU淘汰
return size() > MAX_CACHE_SIZE;
}
}