string是final的,内部用一个final类型的char数组存储数据,它的拼接效率比较低,实际上是通过建立一个StringBuffer,让后台调用append(),最后再将StringBuffer toSting(),每次操作Sting 都会重新建立新的对象来保存新的值.这样原来的对象就没用了,就要被垃圾回收.这也是要影响性能的。
StringBuffer也是final,线程安全的,中采用一个char数组来保存需要append的字符串,char数组有一个初始大小,当append的字符串长度超过当前char数组容量时,则对char数组进行动态扩展,也即重新申请一段更大的内存空间,然后将当前char数组拷贝到新的位置,因为重新分配内存并拷贝的开销比较大,所以每次重新申请内存空间都是采用申请大于当前需要的内存空间的方式,这里是2倍。
StringBuilder,线程不安全。
多态就是:允许基类的指针或引用指向派生类的对象,而在具体访问时实现方法的动态绑定。
原理是java的后期绑定。
抽象:通过特定的实例抽取出共同的特征以后形成的概念的过程,它强调主要特征和忽略次要特征。
封装:把对象的属性和方法结合成一个独立的整体,隐藏实现细节,并提供对外访问的接口。
继承:从已知的一个类中派生出新的一个类,叫子类。子类实现了父类所有非私有化属性和方法,
并能根据自己的实际需求扩展出新的行为。
多态:多个不同的对象对同一消息作出响应,同一消息根据不同的对象而采用各种不同的行为方法。
java虚拟机运行时内存有个叫方法区,主要作用是存储被装载的类的类型信息。每装载一个类的时候,java就会创建一个该类的Class对象实例。我们就可以通过这个实例,来访问这个类的信息。
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。在某些情况下,一个客户不想或者不能直接引用另一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用。
java的静态代理和动态代理(略)
Java对于eqauls方法和hashCode方法是这样规定的:
如果两个对象相同,那么它们的hashCode值一定要相同;
如果两个对象的hashCode相同,它们并不一定相同(上面说的对象相同指的是用eqauls方法比较。)
一般在覆盖equals()方法的同时也要覆盖hashCode()方法,否则,就会违反Object.hashCode的通用约定,从而导致该类无法与所有基于散列值(hash)集合类(HashMap、HashSet和Hashtable)结合在一起正常运行。
map:
hashmap:链地址法,大概思路:通过取key的hashCode值、高位运算、取模运算计算位置,插入位置是通过hascode和eques方法判断key是否一致
①.判断键值对数组table[i]是否为空或为null,否则执行resize()进行扩容;
②.根据键值key计算hash值得到插入的数组索引i,如果table[i]==null,直接新建节点添加,转向⑥,如果table[i]不为空,转向③;
③.判断table[i]的首个元素是否和key一样,如果相同直接覆盖value,否则转向④,这里的相同指的是hashCode以及equals;
④.判断table[i] 是否为treeNode,即table[i] 是否是红黑树,如果是红黑树,则直接在树中插入键值对,否则转向⑤;
⑤.遍历table[i],判断链表长度是否大于8,大于8的话把链表转换为红黑树,在红黑树中执行插入操作,否则进行链表的插入操作;遍历过程中若发现key已经存在直接覆盖value即可;
⑥.插入成功后,判断实际存在的键值对数量size是否超多了最大容量threshold,如果超过,进行扩容。
treemap:TreeMap实现SortedMap接口,能够把它保存的记录根据键排序,默认是按键值的升序排序,也可以指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。在使用TreeMap时,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。
linkedHashMap:保存了记录的插入顺序,在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的,也可以在构造时带参数,按照访问次序排序。
HashTable:因为内部是采用synchronized来保证线程安全的
CocurrentHashMap:利用锁分段技术增加了锁的数目,从而使争夺同一把锁的线程的数目得到控制。
set:
HashSet:内部new了一个hashMap,添加时key放数据,value放一个内部定义的final的Object对象
LinkedHashSet:内部new了一个linkHashMap,添加时key放数据,value放一个内部定义的final的Object对象,遍历时有序
TreeSet:内部new了一个TreeMap,添加时key放数据,value放一个内部定义的final的Object对象.
list:
arraylist和linkedList(略)
copywriteList:CopyOnWriteArrayList适合使用在读操作远远大于写操作的场景里
Integer demo1 = 100;
int demo2 = 100;
Integer demo3 = 10000;
int demo4 = 10000;
Integer demo5 = 100;
Integer demo6 = 100;
Integer demo7 = 10000;
Integer demo8 = 10000;
int demo10=10000;
int demo9=10000;
System.out.println(demo1==demo2);
System.out.println(demo3==demo4);
System.out.println(demo5==demo6);
System.out.println(demo7==demo8);
System.out.println(demo9==demo10);
结果:
true 自动拆箱装箱 比大小
true 自动拆箱装箱 比大小
true java常量池缓存一个引用
false 两个引用
true 比大小
快速失败和安全失败是对迭代器而言的。 快速失败:当在迭代一个集合的时候,如果有另外一个线程在修改这个集合,就会抛出ConcurrentModification异常,java.util下都是快速失败。 安全失败:在迭代时候会在集合二层做一个拷贝,所以在修改集合上层元素不会影响下层。在java.util.concurrent下都是安全失败
ThreadPoolExecutor
构造方法参数讲解
参数名 作用
corePoolSize 队列没满时,线程最大并发数
maximumPoolSizes 队列满后线程能够达到的最大并发数
keepAliveTime 空闲线程过多久被回收的时间限制
unit keepAliveTime 的时间单位
workQueue 阻塞的队列类型
RejectedExecutionHandler 超出 maximumPoolSizes + workQueue 时,任务会交给RejectedExecutionHandler来处理
文字描述
corePoolSize,maximumPoolSize,workQueue之间关系。
当线程池中线程数小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
当线程池中线程数达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行 。
当workQueue已满,且maximumPoolSize > corePoolSize时,新提交任务会创建新线程执行任务。
当workQueue已满,且提交任务数超过maximumPoolSize,任务由RejectedExecutionHandler处理。
当线程池中线程数超过corePoolSize,且超过这部分的空闲时间达到keepAliveTime时,回收这些线程。
当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize范围内的线程空闲时间达到keepAliveTime也将回收。
newCachedThreadPool:无限大小线程池。重用之前已构造的可用线程,如果不存在可用线程,那么会重新创建一个新的线程并将其加入到线程池中。如果线程超过 60 秒还未被使用,就会被中止并从缓存中移除。因此,线程池在长时间空闲后不会消耗任何资源。
FixedThreadPool:复用固定数量的线程 处理一个 共享的无边界队列 。任何时间点,最多有 nThreads 个线程会处于活动状态执行任务。如果当所有线程都是活动时,有多的任务被提交过来,那么它会一致在队列中等待直到有线程可用。如果任何线程在执行过程中因为错误而中止,新的线程会替代它的位置来执行后续的任务。
SingleThreadPool 是通过 java.util.concurrent.Executors 创建的 ThreadPoolExecutor 实例。这个实例只会使用单个工作线程来执行一个无边界的队列。(注意,如果单个线程在执行过程中因为某些错误中止,新的线程会替代它执行后续线程)
wait()和notify/notifyAll方法属于object方法。
wait():wait方法依赖于同步,wait方法则需要释放锁。
yield()、sleep(),join()属于Thread类方法。
sleep():可以直接调用。而更深层次的区别在于sleep方法只是暂时让出CPU的执行权,并不释放锁。
yield():yield方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止。yield方法只是将Running状态转变为Runnable状态
join:join方法就是通过wait方法来将线程的阻塞,如果join的线程还在执行,则将当前线程阻塞起来,直到join的线程执行完成,当前线程才能执行。不过有一点需要注意,这里的join只调用了wait方法,却没有对应的notify方法,原因是Thread的start方法中做了相应的处理,所以当join的线程执行完成以后,会自动唤醒主线程继续往下执行
synchronized 依赖于软件层面jvm
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:
1、如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。
2、如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.
3.如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。
Java SE1.6为了减少获得锁和释放锁所带来的性能消耗,引入了“偏向锁”和“轻量级锁”,
lock无关JVM实现的,依赖于硬件层面,
使用CLH对列(什么前驱,尾部自旋,没记住)实现。原生的CLH队列是用于自旋锁,但Doug Lea把其改造为阻塞锁。
使用上:
synchronized 在成功完成功能或者抛出异常时,虚拟机会自动释放线程占有的锁;而Lock对象在发生异常时,如果没有主动调用unLock()方法去释放锁,则锁对象会一直持有,因此使用Lock时需要在finally块中释放锁;
lock接口锁可以通过多种方法来尝试获取锁包括立即返回是否成功的tryLock(),以及一直尝试获取的lock()方法和尝试等待指定时间长度获取的方法,相对灵活了许多比synchronized;
写锁来提高系统的性能,因为读锁是共享锁,即可以同时有多个线程读取共享资源,而写锁则保证了对共享资源的修改只能是单线程的。
lock可以实现公平锁
原理上:见上面
Atomic原子类分类 :
原子更新基本类型(如AtomicInteger ):CAS操作来保证操作的原子性。
原子更新数组类型(如AtomicLongArray ):CAS操作来保证操作的原子性。
原子更新引用类型(如AtomicReference):
并发容器类:
ConcurrentHashMap:分段锁机制保证并发性
CopyOnWriteArrayList:CopyOnWriteArrayList的缺点,就是修改代价十分昂贵,每次修改都伴随着一次的数组复制;但同时优点也十分明显,就是在并发下不会产生任何的线程安全问题,也就是绝对的线程安全,类似读写分离和最终一致性(数据并不是最新)
特殊功能类:
CountDownLatch:(减计数方式)一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
CyclicBarrier(加计数方式):计数达到指定值时释放所有等待线程
Semaphore:翻译成字面意思为 信号量,Semaphore可以控同时访问的线程个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。
ThreadLocal:为每个线程提供一个独立的变量副本
锁类:
ReentrantLock(可重入锁):可以有公平锁和非公平锁、尝试获得锁(tryLock())等优点
Condition:Lock类可以创建Condition对象,Condition对象用来是线程等待和唤醒线程,需要注意的是Condition对象的唤醒的是用同一个Condition执行await方法的线程,所以也就可以实现唤醒指定类的线程
ReadWriteLock:读读共享,写写互斥,读写互斥
其他:
BlockQueue:同步实现的队列
线程池类:
见上面介绍。
class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
MyISAM:不支持事务,表锁,不支持外键,非聚集索引,数据文件是分离的,适合大量读应用
InnoDB:支持事务,支持外键,表锁和行锁、聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。
其他:NDB和Memory(表中的数据存放在内存中)、Maria(新开发的引擎,设计目标主要是用来取代原有的MyISAM存储引擎)
表锁:略
行锁:
大体两种:共享锁和排它锁
共享锁:允许事务读取一行数据。
排它锁:允许事务删除或者更新一条数据。
行锁是通过给索引上的索引项加锁来实现的
使用:由于InnoDB预设是Row-Level Lock,所以只有「明确」的指定主键,MySQL才会执行Row lock (只锁住被选取的资料例) ,否则MySQL将会执行Table Lock (将整个资料表单给锁住)。
对于innodb来说
使用上:主键索引、唯一索引、普通索引、全文索引(高版本开始支持)、组合索引
原理上:b+树,自适应hash索引,聚集索引(主键默认为辅助索引,如果没有主键,自动选取一个合适的列建立,存储索引和行数据)、辅助索引(存储所在列数据和对应聚合索引位置)
使用1:组合索引左匹配原则
使用2:
mysql EXPLAIN 命令
id: SELECT 查询的标识符. 每个 SELECT 都会自动分配一个唯一的标识符.
select_type: SELECT 查询的类型.
table: 查询的是哪个表
partitions: 匹配的分区
type: join 类型
possible_keys: 此次查询中可能选用的索引
key: 此次查询中确切使用到的索引.
ref: 哪个字段或常数与 key 一起被使用
rows: 显示此查询一共扫描了多少行. 这个是一个估计值.
filtered: 表示此查询条件所过滤的数据的百分比
extra: 额外的信息
使用函数、左匹配、like,or(必须所有的or条件都必须是独立索引),is null,等
四大属性ACID即事务的原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability).。
事务隔离级别:
脏读:指一个事务读取了另外一个事务未提交的数据。
不可重复读:在一个事务内读取表中的某一行数据,多次读取结果不同
幻读:幻读和不可重复读都是读取了另一条已经提交的事务(这点就脏读不同),所不同的是不可重复读查询的都是同一个数据项,而幻读针对的是一批数据整体(比如数据的个数)。
数据库隔离级别:
① Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
② Repeatable read (可重复读):可避免脏读、不可重复读的发生。
③ Read committed (读已提交):可避免脏读的发生。
④ Read uncommitted (读未提交):最低级别,任何情况都无法保证。
第一步:理解sql业务,逻辑上是否可以优化
第二部:explain sql执行计划,目测有没有该走的索引没有走
第三部:查看有无常见影响效率的错误,如null,列上使用函数,or不走组合索引,join之前减少join列数
第四部:增加合适的索引
第五步:还不行,尝试限制需求(如只可以统计指定范围数据)或者其他方式实现(如定时任务定时分析一批)
第六步:数据量超大,只可以考虑分表分库等手段了。
冒泡排序,插入排序,快排。
冒泡:两个循环
插入:从第二个开始,替换
快排:
栈:链表实现,插入头部插入,删除头部删除
队列:链表实现,插入头部插入,出队列尾部去除
第一个:前序遍历(中后遍历类似):
private void qianOut(TwoTreeDemo<Integer> headNode) {
if(headNode!=null){
System.out.print(headNode.value+",");
if(headNode.leftNode!=null){
qianOut(headNode.leftNode);
}if(headNode.rightNode!=null){
qianOut(headNode.rightNode);
}
}
}
第一个:O(1)时间删除指定节点:将该节点的值和next替换为下一节点的,并删除下一节点
第二个:查找链表的倒数第k个节点、链表中间值、两个链表重复处:快慢指针法(见底部附注)
private NodeDemo<Integer> getk(NodeDemo<Integer> headNode,int k) {
if(headNode==null || k<0){
return null;
}
NodeDemo oneNode = headNode;
NodeDemo tweNode = headNode;
int i = 1;
while (oneNode.nextNode != null){
oneNode = oneNode.nextNode;
if(i>=k){
tweNode = tweNode.nextNode;
}
i++;
}
if(i<k){
return null;
}
return tweNode;
}
第三个:反转链表:
private void backNode(NodeDemo<Integer> headNode) {
if(headNode.nextNode == null){//如果是最后一个
this.headNode = headNode; //返回新的头部
return ;
}else {
NodeDemo beforeNode = headNode;
backNode(headNode.nextNode);
headNode.nextNode.nextNode = beforeNode; // 反转
headNode.nextNode = null; // 头结点置为空
}
}
第四个:判断链表有无环
String (Key-Value),Hash(Key-Value),List ,Set,zset,HyperLogLog(不常用)
RDB持久化是指用数据集快照的方式记录redis数据库的所有键值对。
AOF持久化是指所有的命令行记录以redis命令请求协议的格式保存为aof文件。
(弱一致性)
方案1:读取时,先从redis中取,如果没有则从数据库中取出,然后放入redis中。修改删除时,使redis中数据失效,优化点:双删除(写入前后都删除)
方案2:读取mysql的日志binglog
方式一:官方提供的插件
方式二:自己手写,注意点:使用setnx,EXPIRE 或者set四个参数;根据要锁的资源生成key,当前事务生成value;得到锁时设置超时时间;未得到锁设置大概循环时间;未得到锁判断原来的锁有没有设置过期时间;释放锁时使用watch机制或者luna脚本
Redis中的事务(transaction)是一组命令的集合。
reids事务不会回滚,只有全部执行和全部不执行两种,部分语句执行失败不会停止
MULTI(事务开始)EXEC(末尾) watch(观察) unwatch(解除观察)
定时删除:在设置key的过期时间的同时,为该key创建一个定时器,让定时器在key的过期时间来临时,对key进行删除(基本不使用)
惰性删除:key过期的时候不删除,每次从数据库获取key的时候去检查是否过期,若过期,则删除,返回null。(内置策略)
定期删除:每隔一段时间执行一次删除(在redis.conf配置文件设置hz,1s刷新的频率)过期key操作
方案一:会话保持。如使用ip_hash将某个ip定向到某个服务器
方案二:中间件session共享。如tomcat session共享
方案三:独立节点存储session.如session通过spring或者tomcat整合存储到redis中
轮询(默认):每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。
weight(权重):指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。 例如:
ip_hash(ip分):每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。 例如:
fair(第三方):按后端服务器的响应时间来分配请求,响应时间短的优先分配。
amq:ActiveMQ5.3之前的版本,消息存储在一个个文件中
KahaDB:5.4版本之后KahaDB做为默认的持久化方式
JDBC:可以将消息存储到数据库中,例如:Mysql、SQL Server、Oracle、DB2。
LevelDB:从ActiveMQ5.8之后引进的,它和KahaDB非常相似,也是基于文件的本地数据库储存形式.
情况一:发送到broker时消息丢失
解决:先持久到数据库,发送成功后删除或者标记删除。定时任务处理
情况二:broker崩溃
解决:消息持久化
情况三:broker到消费端出问题
解决:逻辑整理使其可以重新发送(如,发送时持久化消息并置状态为已发送,消费成功则置为已消费),当然消费要实现等幂性
可能原因:发送时消息重复、投递时消息重复
消费者的接口要实现等幂性
方法一:全局唯一ID:根据业务的操作和内容生成一个全局ID,在执行操作前先根据这个全局唯一ID是否存在,来判断这个操作是否已经执行。
方法二:去重表,建立一个去重表,去重表中设置唯一索引,如果去重表插入成功则执行
方式三:多版本控制
方案一:保证需要排序的消息按照先后顺序发送到同一个消费队列中,并且这个队列只有一个相应的消费者
方案二:通过业务限制,保证前消息被消费完,才可以发送后消息
DispatcherServlet前端控制器接收发过来的请求,交给HandlerMapping处理器映射器
HandlerMapping处理器映射器,根据请求路径找到相应的HandlerAdapter处理器适配器(处理器适配器就是那些拦截器或Controller)
HandlerAdapter处理器适配器,处理一些功能请求,返回一个ModelAndView对象(包括模型数据、逻辑视图名)
ViewResolver视图解析器,先根据ModelAndView中设置的View解析具体视图
然后再将Model模型中的数据渲染到View上
四种方式:
方式一:编程式事务管理:需要手动编写代码,在实际开发中很少使用
方式二:声明式事务
基于TransactionProxyFactoryBean的方式,需要为每个进行事务管理的类做相应配置
基于AspectJ的XML方式,tx标签不需要改动类,在XML文件中配置好即可
基于注解的方式,@Transactional,配置简单,需要在业务层类中添加注解(使用较多)
TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
默认是单例模式,即scope=“singleton”。
1.singleton单例模式:全局有且仅有一个实例
2.prototype原型模式:每次获取Bean的时候会有一个新的实例
3.request:request表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前有效
4.session:session作用域表示该针对每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前HTTP session内有效HTTP request内有效
5.global session:global session作用域类似于标准的HTTP Session作用域,不过它仅仅在基于portlet的web应用中才有意义。Portlet规范定义了全局Session的概念,它被所有构成某个 portlet web应用的各种不同的portlet所共享。
方法区:线程共享的内存区域,用于存储以被虚拟机加载的类信息、常量、静态变量
运行时常量池:方法区的一部分,用于存放编译期生成的各种字面量和符号引用
程序计数器:它是一块较小的内存空间,它的作用是记录当先线程所执行的字节码的信号指示器。
本地方法栈:本地方法栈与虚拟机栈作用相似,后者为虚拟机执行Java方法服务,而前者为虚拟机用到的Native方法服务。
虚拟机栈(Virtual Machine Stack):每一个线程都有自己的虚拟机栈,这个栈与线程同时创建,它的生命周期与线程相同。虚拟机栈描述的是Java方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(StackFrame)用于存储局部变量表、操作数栈、动态链接、方法出口等信息。
堆(Heap)::存放对象实例
直接内存(Direct Memory):nio使用
Java虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的加载机制。
类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括了:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(using)、和卸载(Unloading)七个阶段。其中验证、准备和解析三个部分统称为连接(Linking)
(看需要是否需要了解)http://www.jfox.info/java-nio-jieshao.html
本质为实现两个进程之间的通信
TCP:传输控制协议,面向连接,可靠。保证数据传输成功。(三次握手和四次分手)
UDP:不可靠。传输速度快。占系统资源少。
BIO:是当发起I/O的读或写操作时,均为阻塞方式,直到应用程序读到了流或者将流写入数据。
NIO:基于事件驱动思想,常采用reactor(反应器)模式。当发起 IO请求时,应用程序是非阻塞的。当SOCKET有流可读或写的时候,由操作系统通知应用程序,应用程序再将流读取到缓冲区或者写入系统。
AIO:同样基于事件驱动的思想,通常采用Proactor(前摄器模式)实现。在进行I/O操作时,直接调用API的read或write,这两种方法均为异步。对于读操作,操作系统将数据读到缓冲区,并通知应用程序,对于写操作,操作系统将write方法传递的流写入并主动通知应用程序。它节省了NIO中遍历事件通知队列的代价。
核心:Channels,Buffers,Selectors
Channel :所有的 IO 在NIO 中都从一个Channel 开始,Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。(FileChannel、SocketChannel、ServerSocketChannel)
Buffers:本质上是一块用于读写的内存,包装成了缓冲区对象,你可以通过allocateDirect()或者allocate()申请内存空间(allocate分配方式产生的内存开销是在JVM中的,而allocateDirect的分配方式产生的开销在JVM之外,以就是系统级的内存分配,使用allocateDirect尤其注意内存溢出问题),Buffer尤其需要理解三个概念,capacity、position、limit,capacity是固定大小,position是当前读写位置,limit是一个类似于门限的值,用于控制读写的最大的位置。Buffer的常用方法有clear、compact、flip等等,还有比如Buffer的静态方法wrap等等,这些需要根据capacity、position、limit的值进行理解。,(ByteBuffer,IntBuffer,LongBuffer等)
Selector:用于检测通道,我们通过它才知道哪个通道发生了哪个事件,所以如果需要用selector的话就需要首先进行register,然后遍历SelectionKey对事件进行处理。它一共有SelectionKey.OP_CONNECT、SelectionKey.OP_ACCEPT、SelectionKey.OP_READ、SelectionKey.OP_WRITE四种事件类型。
bio:
服务器端:
创建ServerSocket对象,绑定监听端口
通过accept()方法监听客户端请求
连接建立后,通过输入流读取客户端发送的请求信息
通过输出流向客户端发送乡音信息
关闭相关资源
客户端:
创建Socket对象,指明需要连接的服务器的地址和端口号
连接建立后,通过输出流想服务器端发送请求信息
通过输入流获取服务器响应的信息
关闭响应资源
nio:
设计模式大体分类:创建型模式,结构型模式,行为型模式
创建型模式:创建对象的同时隐藏创建逻辑,而不是直接使用 new 运算符直接实例化对象。
如:工厂模式,单例模式(至少会一种写法),建造者模式,原型模式
结构型模式:关注类和对象的组合
如:
适配器模式
src类->适配器->dst类
对象适配器模式
适配器拥有src类的实例,实现dst类的接口
桥接模式
类似接口和实现类
过滤器模式
一个过滤维度写一个过滤类,然后写一个与类,写一个或类
扩展时扩展维度就可以
装饰器模式
装饰对象接受所有客户端的请求,并把这些请求转发给真实的对象。这样,就能在真实对象调用前后增加新的功能。
代理模式,
代理类和被代理类实现同一个接口
被代理类中拥有一个代理类的实例
行为型模式:关注对象之间的通信
如:责任链模式,迭代器模式,命令模式
责任链模式
类似拦截器等
命令模式:
将调用者 被调用者 被调用者的行为分开
解释器模式:
每个语法为一个解释器类
迭代器模式:
将集合类的查看遍历和增删改行为分离
1)transient:防止某个属性被序列化
2)CAS算法:有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。这里可能有ABA问题以及如何避免ABA(用版本号)
3)快慢指针法:设置两个指针确认接受此offer