目录
1.@postconstruct @init-method @afterPropertiesSet 顺序
2. JVM常用命令
3.Xss攻击
4.CSRF
5.CORS
6. Mysql索引
7. Mysql事务隔离级别
8. Mysql调优注意事项
8.1 慢查询
9 Mysql 锁研究
9.1 行锁:记录锁(Record Locks)
9.2 间隙锁 [ Gap lock ]: (可重复读隔离级别来解决幻读的问题)
9.3 MVCC
9.4 行锁升级为表锁的情况
10 HashMap
10.1 扩容过程:
10.2 Hashmap为什么长度是2的幂
11 Mysq两种引擎
11.1MyISAM
12 redis原理
13 casandra特点
14. 密码交易安全
15 redis key过大
16. AOP动态代理加反射,静态代理,cglib
17 Volatile
18 CAS算法
19 concurrentHashmap改进
20 有序的map
21 NIO
21.1 Select机制
21.2 Epoll机制
21.3 总结:
22 反射创建对象方式
23 Class.forName和ClassLoader的区别
24 深入ClassLoader
1.为什么要自定义class loader
2.如何打破1双亲委托模式
3.现实应用中打破双亲委托的情况
25 代理实现的三种方式
1.静态代理
2.动态代理
3.CGlib代理
26 Spring AOP 原理
27 JDK1.8新特性
28 Java并行流的实现
29.JVM原理及GC算法
1.CMS收集器(针对老年代,两次stop the world)
2.G1收集器
3.比较一下:
4.JVM回收算法
29 GC类型
30 JVM结构
31 JVM内存分配流程
32 JVM内存溢出
33 JVM重要概念
34 Happens-before规则
35 内存屏障
36 Spring加载流程
37 Spring事务的传播属性
38 线程池
39.Netty线程模型
40.Spring boot启动流程
41.CPU 负载过高排查
42.Voliatile和synchronized
43.线程生命周期
44.Spring单例
45.三个线程输出ABC循环10次
46.原子类原理
47.JUC用过那些并发工具类
48.CAS
49.CountDownLatch
原理
50.ThreadLocal注意点
51 无锁数据结构
52 Countdownlatch和CyclicBarrier
53 AQS
1.AQS的基础CLH队列
2.Volatile和CAS
54 公平锁、非公平锁
55 TCP三次握手
为什么挥手是四次?
Close wait和Time wait
56 页面打开流程
57 长连接
58 HTTPS
59 Session和Cookie
60 分布式唯一ID生成策略
Snowflake算法
UUID
Redis
61 延迟关闭交易
Constructor > @PostConstruct > InitializingBean > init-method
jmap(JVM Memory Map)命令用于生成heap dump文件
jstack用于生成java虚拟机当前时刻的线程快照。线程快照是当前java虚拟机内每一条线程正在执行的方法堆栈的集合,生成线程快照的主要目的是定位线程出现长时间停顿的原因
xxs攻击 xss表示Cross Site Scripting(跨站脚本攻击),通过插入恶意脚本,实现对用户游览器的控制
假如用户提交的数据含有js代码,不做任何处理就保存到了数据库,读出来的时候这段js代码就变成了可执行的代码,将会产生意向不到的效果。一般用户提交的数据永远被认为是不安全的,在保存之前要做对应的处理。这次我就遇到了这个问题,
我提交的内容,或者百度,读出来的时候,将直接弹出1111,或者百度是有效的超链接,这个显然是不行的。
CSRF - Cross-Site Request Forgery - 跨站请求伪造
CSRF攻击之所以能够成功,是因为攻击者可以伪造用户的请求,该请求中所有的用户验证信息都存在于Cookie中,因此攻击者可以在不知道这些验证信息的情况下直接利用用户自己的Cookie来通过安全验证。由此可知,抵御CSRF攻击的关键在于:在请求中放入攻击者所不能伪造的信息,并且该信息不存在于Cookie之中。鉴于此,系统开发者可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务器端建立一个拦截器来验证这个token,如果请求中没有token或者token内容不正确,则认为可能是CSRF攻击而拒绝该请求
SOP就是Same Origin Policy同源策略,指一个域的文档或脚本,不能获取或修改另一个域的文档的属性。也就是Ajax不能跨域访问,我们之前的Web资源访问的根本策略都是建立在SOP上的。它导致很多web开发者很痛苦,后来搞出很多跨域方案,比如JSONP和flash socket
后来出现了CORS-CrossOrigin Resources Sharing,也即跨源资源共享,它定义了一种浏览器和服务器交互的方式来确定是否允许跨域请求。它是一个妥协,有更大的灵活性,但比起简单地允许所有这些的要求来说更加安全。
提升查找速度的关键就在于尽可能少的磁盘I/O,那么可以知道,每个节点中的key个数越多,那么树的高度越小,需要I/O的次数越少,因此一般来说B+Tree比BTree更快,因为B+Tree的非叶节点中不存储data,就可以存储更多的key。
事务的隔离级别分为:未提交读(read uncommitted)、已提交读(read committed)、可重复读(repeatable read)、串行化(serializable)
可能出现脏读,幻读,不可重复读
可能出现不可重复读和幻读
可能出现 幻读,和读已提交读区别是,重复读取的值不变!!!
上面三个隔离级别对同一条记录的读和写都可以并发进行,但是串行化格式下就只能进行读-读并发。只要有一个事务操作一条记录的写,那么其他要访问这条记录的事务都得等着
总体说,三方面: 第一sql语句,第二索引,第三mysql参数
1、查看slowlog,分析slowlog,分析出查询慢的语句。
2、按照一定优先级,进行一个一个的排查所有慢语句。
3、分析top sql,进行explain调试,查看语句执行时间。
4、调整索引或语句本身。
使用explain查看执行情况
使用索引,根据rows看看查询了多少行,判断是不是有问题,是不是缺少索引。
Mysql默认采用的是可重复读(即repeatable read)隔离级别,并且会以Next-Key Lock的方式(即record lock和gap lock的结合)对数据行进行加锁,这样可以有效防止幻读的发生,但是在平时开发过程中,一些不正确的行为可能会导致间隙锁锁定范围变得很大,线程间互相等待从而导致死锁,下面将提供一些解决思路供大家参考:
(1)记录锁, 仅仅锁住索引记录的一行,在单条索引记录上加锁。
(2)record lock锁住的永远是索引,而非记录本身,即使该表上没有任何索引,那么innodb会在后台创建一个隐藏的聚集主键索引,那么锁住的就是这个隐藏的聚集主键索引。
所以说当一条sql没有走任何索引时,那么将会在每一条聚合索引后面加X锁,这个类似于表锁,但原理上和表锁应该是完全不同的
在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。
间隙锁生效:
1. 条件查询,会比如a>1 and a<6那么这个范围都会加间隙所。如果a>3,那么大于3的所有记录都会加间隙锁,甚至包括不存在的记录,为了防止幻读。
2. 如果使用相等条件查询一个不存在的记录,锁住全表,导致其他事务插入阻塞。
比如 select * from where xxx for update. 其中where的条件记录不存在。
再比如 update a set a='11' where b='222' 其中b存在就会全表锁住。为了防止这种情况,b列如果是唯一索引就没有问题,不会升级为全表锁。
间隙锁在InnoDB的唯一作用就是防止其它事务的插入操作,以此来达到防止幻读的发生,所以间隙锁不分什么共享锁与排它锁。如果InnoDB扫描的是一个主键、或是一个唯一索引的话,那InnoDB只会采用行锁方式来加锁,而不会使用Next-Key Lock的方式,也就是说不会对索引之间的间隙加锁。
当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁(GAP LOCK),间隙锁和行锁合称Next-Key Lock
举例来说,假如user表中只有101条记录,其empid的值分别是 1,2,...,100,101,下面的SQL:
select * from user where user_id > 100 for update;
是一个范围条件的检索,InnoDB不仅会对符合条件的user_id值为101的记录加锁,也会对user_id大于101(这些记录并不存在)的“间隙”加锁。
InnoDB使用间隙锁的目的,一方面是为了防止幻读,以满足相关隔离级别的要求,对于上面的例子,要是不使用间隙锁,如果其他事务插入了user_id大于100的任何记录,那么本事务如果再次执行上述语句,就会发生幻读;另外一方面,是为了满足其恢复和复制的需要
对于select from where for update操作,一定要保证where条件字段的值一定存在,不然可能会导致间隙锁锁定的范围变得很大,阻塞别的事务,也有可能会导致死锁
利用MVCC实现一致性非锁定读,保证在同一个事务中多次读取相同的数据返回的结果是一样的,解决了不可重复读问题.
利用Gap Locks和Next-key可以阻止其他事务在锁定区间内插入数据,解决了幻读问题.
MVCC是通过在每行记录后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存行的过期时间(或删除时间)。当然存储的并不是实际的时间值,而是系统版本号(system version number)。每开始一个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较
https://www.jianshu.com/p/f692d4f8a53e
众所周知,MySQL 的 InnoDB 存储引擎支持事务,支持行级锁(innodb的行锁是通过给索引项加锁实现的)。得益于这些特性,数据库支持高并发。如果 InnoDB 更新数据使用的不是行锁,而是表锁呢?是的,InnoDB 其实很容易就升级为表锁,届时并发性将大打折扣了
经过我操作验证,得出行锁升级为表锁的原因之一是: SQL 语句中未使用到索引,或者说使用的索引未被数据库认可(相当于没有使用索引)。未被认可,通常来讲,就是mysql优化器认为直接扫表比索引还快,所以忽略。比如如果一列重复率很高,那么在此列建立索引,会被mysql忽略
因此,table中的元素只有两种情况:
元素hash值第N+1位为0:不需要进行位置调整
元素hash值第N+1位为1:调整至原索引+old Capacity 位置
扩容或初始化完成后,resize方法返回新的table
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
计算出hash值后,进行异或操作,异或操作的对象是hash值无符号右移动16位,高位全部是0.因为hash值是32位的,这样做的目的异或的对象是hash值的高位,也就是hash值的低16和高16进行异或操作,hash值的高16和“全部为0”进行异或,此部分不变化。很巧妙的把高16的变化反应到hash值结果计算中
在多线程使用场景中,应该尽量避免使用线程不安全的HashMap,而使用线程安全的ConcurrentHashMap。那么为什么说HashMap是线程不安全的,下面举例子说明在并发的多线程使用场景中使用HashMap可能造成死循环。代码例子如下(便于理解,仍然使用JDK1.7的环境):
public class HashMapInfiniteLoop {
private static HashMap map = new HashMap(2,0.75f);
public static void main(String[] args) {
map.put(5, "C");
new Thread("Thread1") {
public void run() {
map.put(7, "B");
System.out.println(map);
};
}.start();
new Thread("Thread2") {
public void run() {
map.put(3, "A);
System.out.println(map);
};
}.start();
}
}
https://www.jianshu.com/p/d0ee1ca57f14
单线程 IO多路复用
输入
md5(thirdid+inputpwd+idno)
md5[md5(thirdid+inputpwd+idno)+randomkey]
数据库密码其实是md5(thirdid+inputpwd+idno)加上随机数后的结果
Redis使用过程中经常会有各种大key的情况, 比如:
由于redis是单线程运行的,如果一次操作的value很大会对整个redis的响应时间造成负面影响,所以,业务上能拆则拆,下面举几个典型的分拆方案。
第一个场景:
单个简单的key存储的value很大-------------分拆为多个key-value存储,减轻IO压力,多个redis intance处理
第二个场景:
hash, set,zset,list 中存储过多的元素
类似于场景一种的第一个做法,可以将这些元素分拆。
以hash为例,原先的正常存取流程是 hget(hashKey, field) ; hset(hashKey, field, value)
现在,固定一个桶的数量,比如 10000, 每次存取的时候,先在本地计算field的hash值,模除 10000, 确定了该field落在哪个key上。
newHashKey = hashKey + (*hash*(field) % 10000);
hset (newHashKey, field, value) ;
hget(newHashKey, field)
关键点:
Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this)
package com.puhui.web.advice;
import com.google.common.base.Strings;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynaProxyHello1 implements InvocationHandler {
//调用对象
private Object proxy;
//目标对象
private Object target;
public Object bind(Object target, Object proxy) {
this.target = target;
this.proxy = proxy;
return Proxy.newProxyInstance(this.target.getClass().getClassLoader(), this.target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result = null;
//反射得到操作者的实例
Class clazz = this.proxy.getClass();
//反射得到操作者的Start方法
Method start = clazz.getDeclaredMethod("start", new Class[]{Method.class});
//反射执行start方法
start.invoke(this.proxy, new Object[]{this.proxy.getClass()});
//执行要处理对象的原本方法
method.invoke(this.target, args);
//反射得到操作者的end方法
Method end = clazz.getDeclaredMethod("end", new Class[]{Method.class});
//反射执行end方法
end.invoke(this.proxy, new Object[]{method});
return result;
}
public static void main(String[] args){
IHello proxy=new DynaProxyHello1().bind(new Hello());
}
}
被volatile修饰的变量有如下特性:
①使得变量更新变得具有可见性,只要被volatile修饰的变量的赋值一旦变化就会通知到其他线程,如果其他线程的工作内存中存在这个同一个变量拷贝副本,那么其他线程会放弃这个副本中变量的值,重新去主内存中获取
②产生了内存屏障,防止指令进行了重排序,关于这点的解释,请看下面一段代码:
package com.brickworkers;
public class VolatileTest {
int a = 0; //1
int b = 1; //2
volatile int c = 2; //3
int d = 3; //4
int e = 4; //5
}
在如上的代码中,因为c变量是用volatile进行修饰,那么就会对该段代码产生一个内存屏障,用以保证在执行语句3的时候语句1和语句2是绝对执行完毕的,而且在执行语句3的时候,语句4和语句5肯定没有执行。同时说明一下,在上述代码中虽然保证了语句3的执行顺序不可变换,但是语句1和语句2,语句4和语句5可能发生指令重排序哦。
总结:volatile修饰的变量具有可见性与有序性。
CAS算法
CAS的全称叫“Compare And Swap”,也就是比较与交换,他的主要操作思想是:
首先它具有三个操作数,a、内存位置V,预期值A和新值B。如果在执行过程中,发现内存中的值V与预期值A相匹配,那么他会将V更新为新值A。如果预期值A和内存中的值V不相匹配,那么处理器就不会执行任何操作。CAS算法就是我再技术点中说的“无锁定算法”,因为线程不必再等待锁定,只要执行CAS操作就可以,会在预期中完成。
hashmap在多线程下重哈希(resize)会导致get的时候死循环。JDK 8之前concurrent hashmap采用分段锁的机制。
先简单看下ConcurrentHashMap类在jdk1.7中的设计,其基本结构如图所示:
每一个segment都是一个HashEntry
public class ConcurrentHashMap extends AbstractMap
implements ConcurrentMap, Serializable {
// 将整个hashmap分成几个小的map,每个segment都是一个锁;与hashtable相比,这么设计的目的是对于put, remove等操作,可以减少并发冲突,对
// 不属于同一个片段的节点可以并发操作,大大提高了性能
final Segment[] segments;
// 本质上Segment类就是一个小的hashmap,里面table数组存储了各个节点的数据,继承了ReentrantLock, 可以作为互拆锁使用
static final class Segment extends ReentrantLock implements Serializable {
transient volatile HashEntry[] table;
transient int count;
}
// 基本节点,存储Key, Value值
static final class HashEntry {
final int hash;
final K key;
volatile V value;
volatile HashEntry next;
}
}
改进一:取消segments字段,直接采用transient volatile HashEntry
改进二:将原先table数组+单向链表的数据结构,变更为table数组+单向链表+红黑树的结构。对于hash表来说,最核心的能力在于将key hash之后能均匀的分布在数组中。如果hash之后散列的很均匀,那么table数组中的每个队列长度主要为0或者1。但实际情况并非总是如此理想,虽然ConcurrentHashMap类默认的加载因子为0.75,但是在数据量过大或者运气不佳的情况下,还是会存在一些队列长度过长的情况,如果还是采用单向列表方式,那么查询某个节点的时间复杂度为O(n);因此,对于个数超过8(默认值)的列表,jdk1.8中采用了红黑树的结构,那么查询的时间复杂度可以降低到O(logN),可以改进性能。
put方法做了以下几点事情:
1)如果没有初始化就先调用initTable()方法来进行初始化过程
2)如果没有hash冲突就尝试CAS方式插入
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node(hash, key, value, null)))
break; // no lock when adding to empty bin
}
3)如果还在进行扩容操作就先帮助其它线程进一起行扩容
4)如果存在hash冲突,就加锁来保证put操作的线程安全。
回顾一下hashmap的put过程:
HashMap在put方法中,它使用hashCode()和equals()方法。当我们通过传递key-value对调用put方法的时候,
1)如果没有初始化,调用resize方法初始化,和ConcurrentHashMap不同的是,initTable是线程安全的
2)HashMap使用Key hashCode()和哈希算法来找出存储key-value对的索引。如果索引处为空,则直接插入到对应的数组中,否则,判断是否是红黑树,若是,则红黑树插入,否则遍历链表,若长度超过8,则将链表转为红黑树,转成功之后 再插入。
Hashmap和Hashtable 都不是有序的。
TreeMap和LinkedHashmap都是有序的。(TreeMap默认是key升序,LinkedHashmap默认是数据插入顺序)
TreeMap是基于比较器Comparator来实现有序的。
LinkedHashmap是基于链表来实现数据插入有序的
select的几大缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024
对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。
对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。
对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
package cn.hncu.reflect.test;
import org.junit.Test;
/**
* 1、演示获取Class c对象的三种方法
*@author lxd
*@version 1.0 2017-4-15 下午2:57:05
*@fileName ReflectGetClass.java
*/
public class ReflectGetClass {
/**
* 法1:通过对象---对象.getClass()来获取c(一个Class对象)
*/
@Test
public void get1(){
Person p=new Person("Jack", 23);
Class c=p.getClass();//来自Object方法
}
/**
* 法2:通过类(类型)---任何数据类型包括(基本数据类型)都有一个静态的属性class ,他就是c 一个Class对象
*/
@Test
public void get2(){
Class c=Person.class;
Class c2=int.class;
}
/**
* 法3:通过字符串(类全名 )---能够实现解耦:Class.forName(str)
*/
@Test
public void get3(){
try {
Class c=Class.forName("cn.hncu.reflect.test.Person");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
@CallerSensitive
public static Class> forName(String className)
throws ClassNotFoundException {
Class> caller = Reflection.getCallerClass();
return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}
两种方式的区别:
Class.forName除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
而classloader只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。 forName("")得到的class是已经初始化完成的
loadClass("")得到的class是还没有连接的 一般情况下,这两个方法效果一样,都能装载Class。但如果程序依赖于Class是否被初始化,就必须用Class.forName(name)了。最重要的区别是 forName 会初始化Class,而 loadClass 不会。因此如果要求加载时类的静态变量被初始化或静态块里的代码被执行就只能用 forName,而用 loadClass 只有等创建类实例时才会进行这些初始化。
先看一下线上项目用arthas查看classloader的情况,结果如下:
这三个 ClassLoader 之间形成了级联的父子关系,每个 ClassLoader 都很懒,尽量把工作交给父亲做,父亲干不了了自己才会干。每个 ClassLoader 对象内部都会有一个 parent 属性指向它的父加载器
双亲委派规则可能会变成三亲委派,四亲委派,取决于你使用的父加载器是谁,它会一直递归委派到根加载器
双亲委托的好处是:
比如发现阿里的fastjson自定义了class loader,我们一起看一下。
ASMClassLoader
双亲委派很好地解决了各个类加载器的基础类的同一问题(越基础的类由越上层的加载器进行加载),基础类之所以称为“基础”,是因为它们总是作为被用户代码调用的API,但世事往往没有绝对的完美。如果基础类又要调用回用户的代码,那该么办?JNDI就是这样,为了解决这个问题,引入了线程上下文类加载器
热部署、加解密等功能,自定义classloader
其实就是组合,被代理对象需要定义接口,代理类也要实现同样的接口,所以会产生很多的代理类
JDK封装了一层动态代理。
代理对象不需要实现接口,但是目标对象一定要实现接口,否则不能用动态代理。但是有时候目标对象只是一个单独的对象,并没有实现任何的接口,这个时候就可以使用以目标对象子类的方式类实现代理。
public class DynamicProxy {
public static void main(String[] args) {
Singer singer = new Singer();
Object newProxyInstance = java.lang.reflect.Proxy.newProxyInstance(
singer.getClass().getClassLoader(),
singer.getClass().getInterfaces(),
(proxy, method, ar) -> {
System.out.println("22222");
method.invoke(singer, args);
System.out.println("3333");
return null;
});
ISinger i = (ISinger) newProxyInstance;
i.sing();
}
}
class Singer implements ISinger {
public void sing() {
System.out.println("sing");
}
}
interface ISinger {
public void sing();
}
Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展.
被代理对象不用定义接口。
Spring提供了两种方式来生成代理对象: JDKProxy和Cglib,具体使用哪种方式生成由AopProxyFactory根据AdvisedSupport对象的配置来决定。默认的策略是如果目标类是接口,则使用JDK动态代理技术,否则使用Cglib来生成代理。
AOP 代理则可分为静态代理和动态代理两大类,其中静态代理是指使用 AOP 框架提供的命令进行编译,从而在编译阶段就可生成 AOP 代理类,因此也称为编译时增强;而动态代理则在运行时借助于 JDK 动态代理、CGLIB 等在内存中“临时”生成 AOP 动态代理类,因此也被称为运行时增强。
https://www.cnblogs.com/jacksontao/p/8608291.html
Fork/Join框架属于并行框架,关于Fork/Join框架的一些内容,可以参考这篇文章:Java Fork/Join并行框架。简单来说,Fork/Join框架可以将大的任务切分为足够小的任务,然后将小任务分配给不同的线程来执行,而线程之间通过工作窃取算法来协调资源,提前昨晚任务的线程可以去“窃取”其他还没有做完任务的线程的任务,而每一个线程都会持有一个双端队列,里面存储着分配给自己的任务,Fork/Join框架在实现上,为了防止线程之间的竞争,线程在消费分配给自己的任务时,是从队列头取任务的,而“窃取”线程则从队列尾部取任务。
Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操作,并发模式能够充分利用多核处理器的优势,使用 fork/join 并行方式来拆分任务和加速处理过程。通常编写并行代码很难而且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就可以很方便地写出高性能的并发程序。所以说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物。
Java 8 的排序、取值实现
List<``Integer``> transactionsIds = transactions.parallelStream().
filter(t -> t.getType() == Transaction.GROCERY).
sorted(comparing(Transaction::getValue).reversed()).
map(Transaction::getId).
collect(toList());
CMS收集器:
1 是一种以获取最短回收停顿时间为目标的收集器。
2 基于“标记-清除”算法实现
3 运作过程如下
1)初始标记
2)并发标记
3)重新标记
4)并发清除
初始标记、重新标记这两个步骤仍然需要“stop the world”。初始标记很快。
4 CMS优缺点
主要优点:并发收集、低停顿。
主要缺点:
1)CMS收集器对CPU资源非常敏感。在并发阶段,它虽然不会导致用户线程停顿,但是会因为占用了一部分线程而导致应用程序变慢,总吞吐量会降低。
2)CMS收集器无法处理浮动垃圾,可能会出现“Concurrent Mode Failure(并发模式故障)”失败而导致Full GC产生。
浮动垃圾:由于CMS并发清理阶段用户线程还在运行着,伴随着程序运行自然就会有新的垃圾不断产生,这部分垃圾出现的标记过程之后,CMS无法在当次收集中处理掉它们,只好留待下一次GC中再清理。这些垃圾就是“浮动垃圾”
3)CMS是一款“标记--清除”算法实现的收集器,容易出现大量空间碎片。当空间碎片过多,将会给大对象分配带来很大的麻烦,往往会出现老年代还有很大空间剩余,但是无法找到足够大的连续空间来分配当前对象,不得不提前触发一次Full GC。
CMS收集器在Minor GC时会暂停所有的应用线程,并以多线程的方式进行垃圾回收。在Full GC时不再暂停应用线程,而是使用若干个后台线程定期的对老年代空间进行扫描,及时回收其中不再使用的对象。
PS:开启CMS收集器的方式
-XX:+UseParNewGC -XX:+UseConcMarkSweepGC
老式垃圾回收器(Serial,Parallel,CMS)统一将java堆分成大小固定的三部分:新生代、老年代和永久代。所有内存对象最终都保存在这三个区域内。G1收集器将java堆均分成大小相同的区域(region,1M-32M,最多2000个,最大支持堆内存64G)。一个或多个不连续的区域共同组成eden、survivor或old区,但大小不再固定,这为内存应用提供了极大地弹性。G1垃圾收集过程与CMS类似。G1在堆内存中并发地对所有对象进行标记,决定对象的可达性。经过全局标记,G1了解哪些区域几乎是空的,然后优先收集这些区域,这就是GarbageFirst的命名由来。G1将垃圾收集和内存整理活动专注于那些几乎全是垃圾的区域,并建立停顿预测模型来决定每次GC时回收哪些区域,以满足用户设定的停顿时间。
对于区域的回收通过复制算法实现。在完成标记清理后,G1将这几个区域的存活对象复制到一个单独区域中,实现内存整理和空间释放。这一过程通过多线程并行进行来降低停顿时间,提高吞吐量。通过这样的方式,G1在每次GC过程中持续清理碎片,控制停顿时间满足用户要求。这时过去虚拟机无法做到的。CMS不清理内存碎片(除非通过虚拟机参数设置,在每次或多次FullGC后进行整理),ParallelOld进行全堆整理,会导致较长的停顿时间。
G1不是实时垃圾收集器,它会尽量让停顿时间低于用户设置的停顿时间目标但不能保证一定如此。G1根据历史垃圾收集监测数据来 预测每个区域的回收时间,然后根据用户设定的目标停顿时间决定每次GC时可以回收哪些区域。G1通过这种方式建立比较精确的区域回收时间预测模型
G1的设计初衷是为用户提供大内存、低GC停顿时间的应用解决方案。这意味着堆内存6G或更大,停顿时间稳定且少于0.5秒。
如果应用正在使用CMS或ParallelOld且面临以下问题,推荐将应用迁移至G1
对象存活判断:根据强引用,不是弱引用,软引用
回收算法:
分代收集----划分为young、old区,根据不同的区采取不同的算法,比如young区采用复制算法,old区采用标记整理或者标记-清除算法
CMS-并发收集
G1-----Garbage First 收集,分region,停顿时间基本可控
大多数情况下,对象在新生代中 eden 区分配。当 eden 区没有足够空间进行分配时,虚拟机将发起一次Minor GC。
新生代GC(Minor GC):指发生新生代的的垃圾收集动作,Minor GC非常频繁,回收速度一般也比较快。
老年代GC(Major GC/Full GC):指发生在老年代的GC,出现了Major GC经常会伴随至少一次的Minor GC(并非绝对),Major GC的速度一般会比Minor GC的慢10倍以上
吞吐量=(用户代码的执行时间) / (用户代码的执行时间 + 垃圾收集时间)
吞吐量和响应优先的垃圾收集器选择,所在项目常用的垃圾回收器有两种:
1.Parallel Scavenge + Parallel Old;(又称吞吐量优先,新生代收集器,复制算法,并行收集,面向吞吐量要求,适合做后台的运算),Parallel old 是Parallel Scavenge的老年代版本,标记-整理算法注重吞吐量及cpu资源敏感环境
2.CMS+ParNew;(又称并发标记清除,能降低停顿的时间,适合做前台交互,以提供更快的相应)
CMS和Parallel Scavenge无非共用,CMS等收集器的关注点是尽可能地缩短垃圾收集时,用户线程的停顿时间,而Parallel Scavenge收集器的目的则是达到一个可控制的吞吐量
方法区:类信息,构造方法,静态变量等等信息,属于GC回收区域,存活在老年代,因为不经常变化,所有线程共享
堆:新建对象,属于GC回收区域,所有线程共享。
本地方法栈:natvie方法,线程私有
JVM栈:线程私有,方法的堆栈情况
内存溢出的几种情况:
查看JVM内存情况:jmap -heap `pgrep java`,后面其实是取出pid
sh-4.1# jmap -heap `pgrep java`
Attaching to process ID 1, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.92-b14
using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 2147483648 (2048.0MB)
NewSize = 268435456 (256.0MB)
MaxNewSize = 268435456 (256.0MB)
OldSize = 1879048192 (1792.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 268435456 (256.0MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 268435456 (256.0MB)
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 241631232 (230.4375MB)
used = 210217296 (200.4788360595703MB)
free = 31413936 (29.958663940429688MB)
86.99922367651546% used
Eden Space:
capacity = 214827008 (204.875MB)
used = 201866032 (192.5144500732422MB)
free = 12960976 (12.360549926757812MB)
93.9667846605209% used
From Space:
capacity = 26804224 (25.5625MB)
used = 8351264 (7.964385986328125MB)
free = 18452960 (17.598114013671875MB)
31.156522195904646% used
To Space:
capacity = 26804224 (25.5625MB)
used = 0 (0.0MB)
free = 26804224 (25.5625MB)
0.0% used
concurrent mark-sweep generation:
capacity = 1879048192 (1792.0MB)
used = 128886120 (122.9153823852539MB)
free = 1750162072 (1669.084617614746MB)
6.859117320605686% used
46547 interned Strings occupying 5631608 bytes
利用jstat 进行监控:
sh-4.1# jstat -gccapacity `pgrep java`
NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC MCMN MCMX MC CCSMN CCSMX CCSC YGC FGC
262144.0 262144.0 262144.0 26176.0 26176.0 209792.0 1835008.0 1835008.0 1835008.0 1835008.0 0.0 1142784.0 105600.0 0.0 1048576.0 13184.0 46 0
sh-4.1# jstat -gcold `pgrep java`
MC MU CCSC CCSU OC OU YGC FGC FGCT GCT
105600.0 103411.3 13184.0 12682.1 1835008.0 160268.2 46 0 0.000 2.182
jmap导出内存快照,然后用MAT或者JvisualVM进行分析。
sh-4.1# jmap -dump:format=b,file=20190703.dump `pgrep java`
Dumping heap to /data/20190703.dump ...
Heap dump file created
① 程序次序法则:线程中的每个动作A都happens-before于该线程中的每一个动作B,其中,在程序中,所有的动作B都能出现在A之后。
② 监视器锁法则:对一个监视器锁的解锁 happens-before于每一个后续对同一监视器锁的加锁。
③ volatile变量法则:对volatile域的写入操作happens-before于每一个后续对同一个域的读写操作。
④ 线程启动法则:在一个线程里,对Thread.start的调用会happens-before于每个启动线程的动作。
⑤ 线程终结法则:线程中的任何动作都happens-before于其他线程检测到这个线程已经终结、或者从Thread.join调用中成功返回,或Thread.isAlive返回false。
⑥ 中断法则:一个线程调用另一个线程的interrupt happens-before于被中断的线程发现中断。
⑦ 终结法则:一个对象的构造函数的结束happens-before于这个对象finalizer的开始。
⑧ 传递性:如果A happens-before于B,且B happens-before于C,则A happens-before于C
内存屏障有两个能力:
1. 阻止屏障两边的指令重排序
2. 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效
对Load Barrier来说,在读指令前插入读屏障,可以让高速缓存中的数据失效,重新从主内存加载数据
对Store Barrier来说,在写指令之后插入写屏障,能让写入缓存的最新数据写回到主内存
简单理解一下,以为JVM会进行指令重排序,但是为了不影响结果,保证重排序后结果一直,在影响逻辑的地方需要插入内存屏障,防止屏障两侧代码进行重排序。
另外,读屏障,保证缓冲区数据失效,强制从主内存加载数据。写屏障,保证写入缓存的数据写到主内存,保证可见性。
其实Volatile就是利用这个原理。
https://www.processon.com/view/link/59812124e4b0de2518b32b6e
spring事务:
什么是事务:
事务逻辑上的一组操作,组成这组操作的各个逻辑单元,要么一起成功,要么一起失败.
事务特性(4种):
原子性 (atomicity):强调事务的不可分割.
一致性 (consistency):事务的执行的前后数据的完整性保持一致.
隔离性 (isolation):一个事务执行的过程中,不应该受到其他事务的干扰
持久性(durability) :事务一旦结束,数据就持久到数据库
如果不考虑隔离性引发安全性问题:
脏读 :一个事务读到了另一个事务的未提交的数据
不可重复读 :一个事务读到了另一个事务已经提交的 update 的数据导致多次查询结果不一致.
虚幻读 :一个事务读到了另一个事务已经提交的 insert 的数据导致多次查询结果不一致.
解决读问题: 设置事务隔离级别(5种)
DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
未提交读(read uncommited) :脏读,不可重复读,虚读都有可能发生
已提交读 (read commited):避免脏读。但是不可重复读和虚读有可能发生
可重复读 (repeatable read) :避免脏读和不可重复读.但是虚读有可能发生.
串行化的 (serializable) :避免以上所有读问题.
Mysql 默认:可重复读
Oracle 默认:读已提交
read uncommited:是最低的事务隔离级别,它允许另外一个事务可以看到这个事务未提交的数据。
read commited:保证一个事物提交后才能被另外一个事务读取。另外一个事务不能读取该事物未提交的数据。
repeatable read:这种事务隔离级别可以防止脏读,不可重复读。但是可能会出现幻象读。它除了保证一个事务不能被另外一个事务读取未提交的数据之外还避免了以下情况产生(不可重复读)。
serializable:这是花费最高代价但最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,不可重复读之外,还避免了幻象读(避免三种)。
事务的传播行为
PROPAGION_XXX :事务的传播行为
* 保证同一个事务中
PROPAGATION_REQUIRED 支持当前事务,如果不存在 就新建一个(默认)
PROPAGATION_SUPPORTS 支持当前事务,如果不存在,就不使用事务
PROPAGATION_MANDATORY 支持当前事务,如果不存在,抛出异常
* 保证没有在同一个事务中
PROPAGATION_REQUIRES_NEW 如果有事务存在,挂起当前事务,创建一个新的事务
PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果有事务存在,挂起当前事务
PROPAGATION_NEVER 以非事务方式运行,如果有事务存在,抛出异常
PROPAGATION_NESTED 如果当前事务存在,则嵌套事务执行
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
线程池的队列一定设置为有界队列,否则最大线程数不起作用。无界队列就是不设置上限大小,一直添加成功,直到内存不够。
请求处理模型,其实无非两种,一种是请求/轮询方式,一个是事件驱动方式。传统IO是轮询方式,比如linux的select函数。后来采用性能高效的epoll函数就是事件驱动的机制。
http://www.sohu.com/a/272879207_463994
Select,poll和epoll
(1)select==>时间复杂度O(n)
它仅仅知道了,有I/O事件发生了,却并不知道是哪那几个流(可能有一个,多个,甚至全部),我们只能无差别轮询所有流,找出能读出数据,或者写入数据的流,对他们进行操作。所以select具有O(n)的无差别轮询复杂度,同时处理的流越多,无差别轮询时间就越长。
(2)poll==>时间复杂度O(n)
poll本质上和select没有区别,它将用户传入的数组拷贝到内核空间,然后查询每个fd对应的设备状态, 但是它没有最大连接数的限制,原因是它是基于链表来存储的.
(3)epoll==>时间复杂度O(1)
top -Hp pid可以查看某个进程的线程信息
-H 显示线程信息,-p指定pid
线程安全性包括两个方面,①可见性。②原子性。
从上面自增的例子中可以看出:仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性
Thread.Sleep(0) 并非是真的要线程挂起0毫秒,意义在于这次调用Thread.Sleep(0)的当前线程确实的被冻结了一下,让其他线程有机会优先执行。
Thread.Sleep(0) 是你的线程暂时放弃cpu,也就是释放一些未用的时间片给其他线程或进程使用,就相当于一个让位动作
实质上这种理解是错误的,Java里有个API叫做ThreadLocal,spring单例模式下用它来切换不同线程之间的参数。用ThreadLocal是为了保证线程安全,实际上ThreadLoacal的key就是当前线程的Thread实例。单例模式下,spring把每个线程可能存在线程安全问题的参数值放进了ThreadLocal。这样虽然是一个实例在操作,但是不同线程下的数据互相之间都是隔离的,因为运行时创建和销毁的bean大大减少了,所以大多数场景下这种方式对内存资源的消耗较少,而且并发越高优势越明显。
总的来说就是,单利模式因为大大节省了实例的创建和销毁,有利于提高性能,而ThreadLocal用来保证线程安全性。
另外补充说一句,单例模式是spring推荐的配置,它在高并发下能极大的节省资源,提高服务抗压能力。spring IOC的bean管理器是“绝对的线程安全”
package org.shopping.mall.loan.web.server;
public class MyTest1 {
private static Boolean flagA = true;
private static Boolean flagB = false;
private static Boolean flagC = false;
public static void main(String[] args) {
final Object lock = new Object();
Thread aThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; ) {
synchronized (lock) {
if (flagA) {
//线程A执行
System.out.println("A");
flagA = false;
flagB = true;
flagC = false;
lock.notifyAll();
i++;
} else {
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
});
Thread bThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; ) {
synchronized (lock) {
if (flagB) {
//线程执行
System.out.println("B");
flagA = false;
flagB = false;
flagC = true;
lock.notifyAll();
i++;
} else {
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
});
Thread cThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; ) {
synchronized (lock) {
if (flagC) {
//线程执行
System.out.println("C");
flagA = true;
flagB = false;
flagC = false;
lock.notifyAll();
i++;
} else {
try {
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
});
cThread.start();
bThread.start();
aThread.start();
}
}
CAS+Volatile
cas操作存在ABA问题,值虽然和原来一样,但是其实已经被修改。如果解决?不能只是比较值,可以增加版本号。利用乐观锁实现
/**
* CAS head field. Used only by enq.
*/
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
/**
* CAS tail field. Used only by enq.
*/
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
/**
* CAS waitStatus field of a node.
*/
private static final boolean compareAndSetWaitStatus(Node node,
int expect,
int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset,
expect, update);
}
/**
* CAS next field of a node.
*/
private static final boolean compareAndSetNext(Node node,
Node expect,
Node update) {
return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
await:尝试获取资源,代码主要逻辑是判断state是否为0,如果为0,则可以继续,如果state不为0,则把当前线程构建node,加入队列等待。
countdown:自旋判断(就是for一直循环),判断是否是0,是0,中断不需要进行。如果不是0,进行CAS操作进行减1.如果减去1之后的nextc是0,unblock所有的等待线程
https://www.jianshu.com/p/de164687c880
final ExecutorService pool = Executors.newFixedThreadPool(10);
String channelCode = ChannelContext.getChannelConfig().getChannelCode();
CompletableFuture> c1 = CompletableFuture.supplyAsync(() -> {
ChannelContext.removeAll();
ChannelContext.setChannelConfigData(channelConfig);
return saveSdkInfo("contacts", bldSaveLoanInfoRequest, user, channelCode);
}, pool).whenComplete((v, e) -> {
if (e != null) {
logger.error("saveSdkInfo contacts", e);
throw new RuntimeException(e);
}
}).exceptionally(e -> {
logger.error("saveSdkInfo contacts", e);
throw new RuntimeException(e);
});
package com.puhui.common.channelconfig.session;
import com.puhui.common.channelconfig.entity.ChannelConfig;
/**
* @author : banxiaohua
* @date : 2019/4/1 7:13 PM
*/
public class ChannelContext {
/**
* 渠道信息
*/
private static ThreadLocal CHANNEL_CONFIG_DATA = new ThreadLocal<>();
/**
* 设置渠道配置
* @param channelConfigData channelConfigData
*/
public static void setChannelConfigData(ChannelConfig channelConfigData) {
CHANNEL_CONFIG_DATA.set(channelConfigData);
}
/**
* 获取渠道配置
* @return ChannelConfig
*/
public static ChannelConfig getChannelConfig() {
return CHANNEL_CONFIG_DATA.get();
}
/**
* 获取渠道id
* @return Long
*/
public static Integer getChannelId() {
return CHANNEL_CONFIG_DATA.get().getId();
}
/**
* remove
*/
public static void removeAll() {
CHANNEL_CONFIG_DATA.remove();
}
}
以上基本上就是所有的无锁队列的技术细节,这些技术都可以用在其它的无锁数据结构上。
1)无锁队列主要是通过CAS、FAA这些原子操作,和Retry-Loop实现。
2)对于Retry-Loop,我个人感觉其实和锁什么什么两样。只是这种“锁”的粒度变小了,主要是“锁”HEAD和TAIL这两个关键资源。而不是整个数据结构
https://www.cnblogs.com/alantu2018/p/8469168.html
CountDownLatch的计数器只能使用一次,而CyclicBarrier的计数器可以使用reset()方法重置,可以使用多次,所以CyclicBarrier能够处理更为复杂的场景;
CyclicBarrier还提供了一些其他有用的方法,比如getNumberWaiting()方法可以获得CyclicBarrier阻塞的线程数量,isBroken()方法用来了解阻塞的线程是否被中断;
CountDownLatch允许一个或多个线程等待一组事件的产生,而CyclicBarrier用于等待其他线程运行到栅栏位置。
CyclicBarrier调用dowait方法进行等待,等待的时候利用ReentrantLock判断是否count减为0,或者有其他线程中断,
CountDownLatch利用AQS实现,本质代码是voliatile和CAS原子指令
提供了一个基于FIFO队列,可以用于构建锁或者其他相关同步装置的基础框架。该同步器(以下简称同步器)利用了一个int来表示状态,期望它能够成为实现大部分同步需求的基础。使用的方法是继承,子类通过继承同步器并需要实现它的方法来管理其状态,管理的方式就是通过类似acquire和release的方式来操纵状态。然而多线程环境中对状态的操纵必须确保原子性,因此子类对于状态的把握,需要使用这个同步器提供的以下三个方法对状态进行操作:
被动关闭方可能还需要发送一些数据后,再发送FIN报文表示同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的
CLOSE_WAIT过多的原因出在程序本身,是由于被动关闭连接处理不当导致的。回到程序中检查连接关闭处理
一次请求大致过程包括:域名解析--> 发起TCP的3次握手 --> 建立TCP连接后发起http请求 --> 服务器响应http请求--> 浏览器得到html代码 --> 浏览器解析html代码,并请求html代码中的资源(如js、css、图片等) --> 浏览器对页面进行渲染呈现给用户
网络上很多文章都是误人子弟,根本没有说明白这个概念。这里LZ要强调一下,HTTP协议是基于请求/响应模式的,因此只要服务端给了响应,本次HTTP连接就结束了,或者更准确的说,是本次HTTP请求就结束了,根本没有长连接这一说。那么自然也就没有短连接这一说了。
之所以网络上说HTTP分为长连接和短连接,其实本质上是说的TCP连接。TCP连接是一个双向的通道,它是可以保持一段时间不关闭的,因此TCP连接才有真正的长连接和短连接这一说。
http协议默认是长连接,header设置connection:keep-alive就是启用长连接。
HTTP协议采用“请求-应答”模式,不开启KeepAlive模式时,每个req/res客户端和服务端都要新建一个连接,完成之后立即断开连接(HTTP协议为无连接的协议);当开启Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服务器端的连接持续有效,当出现对服务器的后继请求时,Keep-Alive功能避免了建立或者重新建立连接。
The client cannot specify the timeout, it is the server configuration that determines the maximum timeout value. The extra Keep-Alive header can inform the client how long the server is willing to keep the connection open (timeout=N value) and how many requests you can do over the same connection (max=M) before the server will force a close of the connection
1.存放的位置
cookie保存在客户端,session一般保存在服务器端的文件系统或数据库或mamcache
2.安全性
由于session存放在服务器端,而客户端可以集中采用软、硬件技术保证安全性,所以cookie的安全性较session弱
3.网络传输量
cookie需通过网络实现客户端与服务器端之间的传输,而session保存在服务器端,无需传输
4.生存时间(以设置24分钟为例)
(1)cookie的生命周期是累计的。从创建的时候就开始计时,24分钟后cookie生命周期结束,cookie自动失效
(2)session的生命周期是间隔的,从创建时开始计时,比如在24分钟内(php.ini默认session的失效时间就是1440s,即24m)没有访问过session(指没有执行含session的文件),那么session信息就自动无效,但如果在24分钟之内,比如第23分钟访问过session,那么它的生命周期将重新开始计算。
1.全局唯一性:不能出现重复的ID,最基本的要求。
2.趋势递增:MySQL InnoDB引擎使用的是聚集索引,由于多数RDBMS使用B-tree的数据结构来存储索引数据,在主键的选择上面我们应尽量使用有序的主键保证写入性能。
3.单调递增:保证下一个ID一定大于上一个ID。
4.信息安全:如果ID是连续递增的,恶意用户就可以很容易的窥见订单号的规则,从而猜出下一个订单号,如果是竞争对手,就可以直接知道我们一天的订单量。所以在某些场景下,需要ID无规则。
snowflake的结构如下(每部分用-分开):
0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年),然后是5位datacenterId和5位workerId(10位的长度最多支持部署1024个节点) ,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
一共加起来刚好64位,为一个Long型。(转换成字符串长度为18)
snowflake生成的ID整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由datacenter和workerId作区分),并且效率较高。据说:snowflake每秒能够产生26万个ID
缺点
UUID由以下几部分的组合:
(1)当前日期和时间。
(2)时钟序列。
(3)全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。
标准的UUID格式为:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx (8-4-4-4-12),以连字号分为五段形式的36个字符,示例:550e8400-e29b-41d4-a716-446655440000。Java标准类库中已经提供了UUID的API。
缺点
public String createRequestId(String channel) {
// 生成编号 产品号(3位)+日期(8位)+每日从1开始自增长数(7位)
StringBuilder num = new StringBuilder(channel);
String dateStr = DateUtils.dateToStr(new Date(), 2);
num.append(dateStr);
Long count = redisTmp.incr(Constants.REDIS_APPLYNO + channel + dateStr);
if (count.intValue() != 0) {
num.append(String.format("%07d", count));
} else {
return "";
}
return num.toString();
}
利用redis的原子操作,incr函数进行自增运算。同样涉及时钟不一致问题。
JDK7提供了7个阻塞队列。分别是
/**
* Priority queue represented as a balanced binary heap: the two
* children of queue[n] are queue[2*n+1] and queue[2*(n+1)]. The
* priority queue is ordered by comparator, or by the elements'
* natural ordering, if comparator is null: For each node n in the
* heap and each descendant d of n, n <= d. The element with the
* lowest value is in queue[0], assuming the queue is nonempty.
*/
transient Object[] queue; // non-private to simplify nested class access