ConcurrentHashMap
1.7的get方法
首先会定位到segment(本身是可重入锁):对key的hashcode进行再散列值的高位取模
然后定位table:key的hashcode进行再次取值取模
依次扫描链表,找到元素返回值,找不到返回null
为什么HashMap在多线程情况下不适用:多线程情况下put方法会引起死循环,hashMap里面的Entry链表会产生环形数据结构,这就导致链表上的下一个节点永远不为空。
位运算
位运算控制CRUD权限
@Data
public class TestDemo {
//查询权限
public static final int ALLOW_SELECT = 1 << 0;//0001
//插入权限
public static final int ALLOW_INSERT = 1 << 1;//0010
//更新权限
public static final int ALLOW_UPDATE = 1 << 2;//0100
//删除权限
public static final int ALLOW_DELETE = 1 << 3;//1000
//权限状态
private int flag;
//设置用户权限
public void setPer(int per) {
flag = per;
}
//再增加用户权限,1个或者多个
public void enable(int per) {
flag = flag | per;
}
//删除用户权限
public void disable(int per) {
flag = flag & ~per;
}
//判断用户权限
public Boolean isAllow(int per) {
return ((flag & per) == per);
}
//判断用户没有的权限
public Boolean isNotAllow(int per) {
return ((flag & per) == 0);
}
@Test
public void testDemo1() {
//默认全部权限
int flag = 15;
TestDemo testDemo = new TestDemo();
testDemo.setFlag(flag);
//去除掉删除和更新权限
testDemo.disable(ALLOW_DELETE | ALLOW_UPDATE);
System.out.println("ALLOW_SELECT=" + testDemo.isAllow(ALLOW_SELECT));
System.out.println("ALLOW_DELETE=" + testDemo.isAllow(ALLOW_DELETE));
System.out.println("ALLOW_UPDATE=" + testDemo.isAllow(ALLOW_UPDATE));
System.out.println("ALLOW_INSERT=" + testDemo.isAllow(ALLOW_INSERT));
}
}
ALLOW_SELECT=true
ALLOW_DELETE=false
ALLOW_UPDATE=false
ALLOW_INSERT=true
JDK1.8后的实现
与1.7相比
1.取消了segment数组,直接采用tabe数组,好处在于锁的粒度更小,减少并发冲突的概率。
2.table数组+链表+红黑树的结构。纯链表时间复杂度为O(n),红黑树时间复杂度为O(logn),性能有极大的提示。其次当链表的长度超过8时候,就会将链表转为红黑树
主要数据结构和关键变量
sizeCtl:
1.如果小于0说明进行初始化或者进行扩容。-1表示正在初始化,-n表示有n-1个线程正在进行扩容。
2.等于0表示还没有被初始化
3.大于0的数是表示初始化或者进行下一次扩容阈值
TreeNode,TreeBin
TreeNode用在红黑树下面。TreeBin是实际放在table数组中的,代表这个红黑树的跟。
数据定位
1.7hash算法:高位余上整个hash值,定位到segment,然后用整个hash的key来定位table
1.8hash算法:高位异或hash值,来定位table上的值
红黑树和链表转换
当列表长度大于8个时候,就会转红黑树,当红黑树的长度小于六个时候,会将红黑树转为列表
Put时候做了什么
数据的实际初始化
Get时候做了什么
扩容
最终调用transfer进行扩容,而且扩容一定是2n次方的整数(减少移动和重排的次数)
更多并发容器
ConcurrentSkipListMap, ConcurrentSkipListSet
对应TreeMap和TreeSet有序容器的并发版本。
链表和跳表
链表:普通的链表插入删除效率很快,但是查询缺很慢,如果我要找90所在的位置,那它需要进行20,30,40,50,70,90这几次遍历才能找到90。
跳表(概率数据结构):
1.以空间换取时间类似于数据库的索引。
2.当新增一个元素后,会通过抛色子方式,决定上一层是否为索引,如果为false直至最顶层
3.跳表加快了查询的效率,如果我要查70,它流程是通过顶层的索引往下找。只需要查询三次即可。
为什么hashMap不适应跳表
跳表操作是采用空间换时间的操作,hashmap的空间利用率很低在30%左右,如果在加上跳表的索引,那么它的空间使用率会急剧下降
ConcurrentLinkedQueue
无界非阻塞队列,底层是链表,遵循先进先出原则。是linkedList的并发版本。
add和offer方法将元素插入到队列的底部,peek拿头部的数据,但不移除,poll拿头部数据并且移除
阻塞队列
概念
对队列满的时候,插入的元素的线程将被阻塞,直到队列不满。
当队列为空的时候,获取元素线程被阻塞,直至队列有数据。
生产者和消费者
举个列子:生产者负责生产汽油,消费者负责加油,当汽油的生产量和加油消耗量不成正比时候,这时候会出现两者之间的能力不匹配的问题。所以这时候加一个容器[加油站],每次加油去加油站,而每次生产的汽油放进加油站,解决了加油和生产油两者直接的耦合。
延迟队列实战
使用DelayQueue实现一个订单延超时取消系统
/*
* 实现一个订单延迟功能,实现Delayed接口。重写compareTo,getDelay方法
* 这里框架针对于超时取消通用
* */
public class ItemVo<T> implements Delayed {
//数据
private T date;
//订单的过期时间 ms单位
private long activeTime;
public ItemVo(T date, long activeTime) {
this.date = date;
//将时间转换为ns,加上当前纳秒数
this.activeTime = TimeUnit.NANOSECONDS.convert(activeTime, TimeUnit.MILLISECONDS) + System.nanoTime();
}
public T getDate() {
return date;
}
public long getActiveTime() {
return activeTime;
}
@Override
public int compareTo(Delayed o) {
long d = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
return (d == 0) ? 0 : (d > 0 ? 1 : -1);
}
//返回元素剩余的时间
@Override
public long getDelay(TimeUnit unit) {
long d = unit.convert(this.activeTime - System.nanoTime(), TimeUnit.NANOSECONDS);
return d;
}
}
/*
* 订单实体类
* */
public class OrderBean {
//订单号
private String code;
//订单金额
private Integer money;
public OrderBean(String code, Integer money) {
this.code = code;
this.money = money;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Integer getMoney() {
return money;
}
public void setMoney(Integer money) {
this.money = money;
}
}
public class TestDemo {
private DelayQueue<ItemVo<OrderBean>> delayQueue;
public TestDemo(DelayQueue<ItemVo<OrderBean>> delayQueue) {
this.delayQueue = delayQueue;
}
//插入数据
public Thread setOrder() {
return new Thread(() -> {
//5s后过期
OrderBean tb = new OrderBean("TB2022040401", 888);
ItemVo<OrderBean> tbVo = new ItemVo<>(tb, 2000);
delayQueue.add(tbVo);
System.out.println("淘宝订单2s后到期:" + tb.getCode());
//6s后过期
OrderBean jd = new OrderBean("JD2022040401", 888);
ItemVo<OrderBean> jdVo = new ItemVo<>(jd, 2000);
delayQueue.add(jdVo);
System.out.println("京东订单2s后到期:" + jd.getCode());
});
}
//获取过期的数据
public Thread getOrder() {
return new Thread(() -> {
while (true) {
try {
ItemVo<OrderBean> take = delayQueue.take();
OrderBean date = (OrderBean) take.getDate();
System.out.println("拿到过期的订单:" + date.getCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
public static void main(String[] args) {
DelayQueue<ItemVo<OrderBean>> itemVos = new DelayQueue<>();
TestDemo testDemo = new TestDemo(itemVos);
Thread setThread = testDemo.setOrder();
Thread getThread = testDemo.getOrder();
setThread.start();
getThread.start();
}
}
淘宝订单2s后到期:TB2022040401
京东订单2s后到期:JD2022040401
拿到过期的订单:TB2022040401
拿到过期的订单:JD2022040401