动态代理与静态代理

欢迎查看Eetal的第十七篇博客–动态代理与静态代理

静态代理—硬编码

没啥好说的,就是像装饰模式一样

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class StaticProxy extends Object{

Object object;

public StaticProxy(Object object) {
this.object = object;
}

@Override
public int hashCode() {
System.out.println("proxy decorate start...");
int hashCode = object.hashCode();
System.out.println("proxy decorate end...");
return hashCode;
}

public static void main(String[] args) {
Object proxy = new StaticProxy(new Object());
System.out.println(proxy.hashCode());
}
}

动态代理

JDK动态代理—反射、Proxy、InvocationHandler

jdk动态代理要求对象必须实现接口
并且仅会代理接口方法以及equals,hashCode和toString三个Object类的方法
生成的代理类也仅是实现这些接口和继承Proxy所以接口里没有的方法在代理对象里不存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public class DynamicProxy {
static class BAInvocationHandler implements InvocationHandler{

private Object realObj;

public BAInvocationHandler(Object realObj) {
this.realObj = realObj;
}

public void doBefore() {
System.out.println("before ...");
}

public void doAfter() {
System.out.println("after ...");
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//proxy为代理对象,所以此处不能调用proxy的方法否则会栈溢出死锁
//proxy是在newProxyInstance时生成并在调用时传进来的,因为invocationHandler与Proxy是聚合模式
//所以在invocation中获取不到代理对象,只能通过这种方式传递进来
doBefore();
Object result = method.invoke(realObj, args);
System.out.println("invoke result :"+result);
doAfter();
return result;
}

}
static <T>T getInstance(Object realObj,Class<T>... clazz) {
//第一个T泛型方法
//clazz 该代理类实现的接口
return (T)Proxy.newProxyInstance(clazz[0].getClassLoader(), clazz,new BAInvocationHandler(realObj));
}
public static void main(String[] args) {
Object o = getInstance(new Object(),Serializable.class);
o.hashCode();
System.out.println(o instanceof Proxy);

}
}

由于jdk动态代理基于接口的特性,使得其只能代理接口对象的方法,也就是必须实现接口
至于为什么,我个人的理解是在运行时
1.其要继承Proxy,因为Proxy封装了动态代理的底层实现,所以Proxy不能是接口,因此只能通过继承来组合(通过聚合或者组合还是离不开要修改字节码)
2.jdk反射只能得到每一个class,interface等的field的名称,返回值类型,形参类型,方法体是得不到的,
否则就得通过读取修改字节码来生成新的class的字节码文件(cglib就是这么做的),而读取解析修改字节码效率肯定低一点
因此jdk代理只代理接口对象,实际需要一个原对象,再生成一个代理对象,使用的是代理对象
注意是对象因为原对象一些在接口中不存在的属性在代理对象中是不存在的,这也是需要getter、setter存在的一个原因

CGLIB动态代理—修改字节码,生成新的子类Class

当面对特殊的场景,比如就是没有实现任何接口,或者希望获取到所有属性,得到一个子类型的类型,就需要通过cglib了
cglib的做法是,通过读取修改字节码,创建一个继承了原有class的子类,所以cglib是代理类,代理方法,而不是代理对象
cglib会代理方法中所有不是final类型的非静态方法方法(因为静态方法不会使用代理对象和代理类)
使用时是直接使用代理类去创建对象,只有一个对象,因此效率较jdk代理低一点
在spring中就是根据要代理的类是否有实现接口,而决定使用cglib还是jdk动态代理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class CGLIBDynamicProxy {
static class Test{
public final void finalMethod() {
System.out.println("final");
}
public void method() {
System.out.println("method");
}
public static void staticMethod(){
System.out.println("staticMethod...");
}
}
static class BAInterceptor implements MethodInterceptor{

public void doBefore() {
System.out.println("\nbefore ...");
}

public void doAfter() {
System.out.println("after ...\n");
}

@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//proxy---代理类对象
//method---代理后的方法
//args---参数
//methodProxy---方法的代理对象
//不能直接使用 method.invoke,因为会再调用invoke导致死锁stackOverFlow
doBefore();
//使用父类方法,也就是原类方法获取返回值
Object result = methodProxy.invokeSuper(proxy, args);
System.out.println("invoke super result..."+result);
doAfter();
return result;
}


}
static <T> T getInstance(Class<T> clazz) {
//第一个T泛型方法
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(new BAInterceptor());
return (T) enhancer.create();
}
public static void main(String[] args) {
Test test = getInstance(Test.class);
test.hashCode();
test.finalMethod();
test.method();
test.staticMethod();
}
}

请移步

个人主页: yangyitao.top

手撕B树

欢迎查看Eetal的第十六篇博客–手撕B树

B树

B树和B+树都是多路查找树。具备分支多层数少的优点常用于数据库索引存储
BTree大量应用在数据库(mongodb)和文件系统当中,B+Tree经常用于数据库索引(空间局部性原理)
B树代表每个节点可存储的数据节点不超过m-1,且每个节点的子节点不超过m个的树。根节点的子节点为0或至少为2
B+树与B树区别就是B+树只在没有子节点的底层节点存储数据而其他包含子节点的父节点都是不存储数据值存储索引值
假设m为4我们就说这是一颗4叉的B树,其每个节点可以存储最多3个数据节点,最多含有4个子节点

特点

数据保存在节点
一个节点可以至有多m-1个数据节点
根结点如果有子女则至少有两个子女
除根结点以外的所有结点(不包括叶子结点)的度数正好是当前结点保存的数据节点总数加1,故内部子树个数 k 满足:┌m/2┐ <= k <= m
所有的叶子结点都位于同一层。(叶子节点指的是指向null)
每个非根节点所包含的数据节点个数 j 满足:┌m/2┐ - 1 <= j <= m - 1;// ┌m/2┐ ==》m/2向上取整

结构图

b-tree

构造B树

我们先了解下其几点重要构建要求
1.保持所有的叶子结点都位于同一层
2.一个节点可以至有多m-1个数据节点
3.每个非根节点所包含的数据节点个数 j 满足:┌m/2┐ - 1 <= j <= m - 1;// ┌m/2┐ ==》m/2向上取整
基于这些条件,B树的插入是从底部插入,先假设可以直接插入找到的节点,这时不破坏要求1
插入后因为新增数据节点如果破坏了要求2,进行裂变
裂变为了满足要求3从中间裂变
裂变出来的节点插入当前结点父节点(递归操作),因为是向上裂变插入,所以不破坏规则1

代码实现

可以保存多个数据节点的节点类

1
2
3
4
5
6
7
8
class MultiNode{
int nodeCounter = 0;//数据节点总数
Node head;//数据节点链表头
Node tail;//数据节点链表尾
MultiNode parent;//父节点
Node greaterParent;//大于当前节点的数据节点
Node lesserParent;//小于当前节点的数据节点
}

数据节点类

1
2
3
4
5
6
7
class Node{
int num;//索引大小
MultiNode greater;//大于
MultiNode lesser;//小于
Node prev;
Node next;
}

裂变操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
private Node fissAndReturnBreakNode(MultiNode multiNode,int breakPoint) {
if(breakPoint<1 || breakPoint>=multiNums) {
return null;
}
//裂变
if(multiNode.greaterParent!=null) {
//父节点断开
multiNode.greaterParent.lesser = null;
multiNode.greaterParent = null;
}
if(multiNode.lesserParent!=null) {
//父节点断开
multiNode.lesserParent.greater = null;
multiNode.lesserParent = null;
}
Node beakNode = multiNode.head;
for(int i=0;i<breakPoint;i++)
beakNode = beakNode.next;
MultiNode newMultiNode = new MultiNode();
newMultiNode.head = beakNode.next;
newMultiNode.tail = multiNode.tail;
multiNode.tail = beakNode.prev;
newMultiNode.nodeCounter = multiNode.nodeCounter - breakPoint -1;
multiNode.nodeCounter = breakPoint;
beakNode.next = beakNode.prev = newMultiNode.head.prev = multiNode.tail.next = null;
if(beakNode.lesser!=null) {
beakNode.lesser.lesserParent = multiNode.tail;
multiNode.tail.greater = beakNode.lesser;
}
if(beakNode.greater!=null) {
beakNode.greater.greaterParent = newMultiNode.head;
newMultiNode.head.lesser = beakNode.greater;
}
beakNode.lesser = multiNode;
multiNode.greaterParent = beakNode;
beakNode.greater = newMultiNode;
newMultiNode.lesserParent = beakNode;
newMultiNode.parent = multiNode.parent;
return beakNode;
}

插入操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
public void addNum(int num) {
Node node = new Node();
node.num = num;
addNode(node,root);
}
protected MultiNode addNode(Node node,MultiNode multiNode) {
if(node == null) {
return multiNode;
}
else if(multiNode == null) {
root = multiNode = new MultiNode();
multiNode.tail = multiNode.head = node;
multiNode.nodeCounter++;
return multiNode;
}
else{
Node head = multiNode.head;
do{
if(head.num > node.num) {
if(head.lesser!=null) {
addNode(node,head.lesser);
}
else {
if(head.prev!=null) {
if(head.lesser != null) {
node.greater = head.lesser;
head.lesser.lesserParent = node;
}
if(head.prev.greater!=null) {
node.lesser = head.prev.greater;
head.prev.greater.greaterParent = node;
}
head.prev.next = node;
node.prev = head.prev;
}
else {
if(head.lesser!=null) {
node.greater = head.lesser;
head.lesser.lesserParent = node;
}
multiNode.head = node;
}
node.next = head;
head.prev = node;
if(++multiNode.nodeCounter == multiNums) {
Node breakNode = fissAndReturnBreakNode(multiNode,multiNums/2);
addNode(breakNode,multiNode.parent);
}
}
return multiNode;
}
}while((head = head.next)!=null);

if(multiNode.tail.greater!=null) {
addNode(node,multiNode.tail.greater);
}
else {
multiNode.tail.next = node;
node.prev = multiNode.tail;
if(multiNode.tail.greater!=null) {
node.lesser = multiNode.tail.greater;
multiNode.tail.greater.greaterParent = node;
}
multiNode.tail = node;
if(++multiNode.nodeCounter == multiNums) {
Node breakNode = fissAndReturnBreakNode(multiNode,multiNums/2);
addNode(breakNode,multiNode.parent);
}
}
return multiNode;
}
}

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package zcy;

import java.util.concurrent.LinkedBlockingQueue;

public class BTree {

public BTree(int multiNums) {
this.multiNums = multiNums;
}

volatile MultiNode root;//根节点

int multiNums;//叉的数目

public static void printTree(MultiNode node) {
if(node == null)
return;
System.out.println(node+": ");
LinkedBlockingQueue<MultiNode> queue = new LinkedBlockingQueue<MultiNode>();
Node head = node.head;
while(head!=null) {
if(head.lesser!=null)
queue.add(head.lesser);
if(head.greater!=null)
queue.add(head.greater);
System.out.println(head.num+" -> lesser:"+head.lesser+", ->greate:"+head.greater);
head = head.next;
}
System.out.println();
queue.forEach(multiNode->printTree(multiNode));
}
class MultiNode{
int nodeCounter = 0;//数据节点总数
Node head;//数据节点链表头
Node tail;//数据节点链表尾
MultiNode parent;//父节点
Node greaterParent;//大父节点
Node lesserParent;//大父节点
}

class Node{
int num;//索引大小
MultiNode greater;//大于
MultiNode lesser;//小于
Node prev;
Node next;
}

public void addNum(int num) {
Node node = new Node();
node.num = num;
addNode(node,root);
}

private Node fissAndReturnBreakNode(MultiNode multiNode,int breakPoint) {
if(breakPoint<1 || breakPoint>=multiNums) {
return null;
}
//裂变
if(multiNode.greaterParent!=null) {
//父节点断开
multiNode.greaterParent.lesser = null;
multiNode.greaterParent = null;
}
if(multiNode.lesserParent!=null) {
//父节点断开
multiNode.lesserParent.greater = null;
multiNode.lesserParent = null;
}
Node beakNode = multiNode.head;
for(int i=0;i<breakPoint;i++)
beakNode = beakNode.next;
MultiNode newMultiNode = new MultiNode();
newMultiNode.head = beakNode.next;
newMultiNode.tail = multiNode.tail;
multiNode.tail = beakNode.prev;
newMultiNode.nodeCounter = multiNode.nodeCounter - breakPoint -1;
multiNode.nodeCounter = breakPoint;
beakNode.next = beakNode.prev = newMultiNode.head.prev = multiNode.tail.next = null;
if(beakNode.lesser!=null) {
beakNode.lesser.lesserParent = multiNode.tail;
multiNode.tail.greater = beakNode.lesser;
}
if(beakNode.greater!=null) {
beakNode.greater.greaterParent = newMultiNode.head;
newMultiNode.head.lesser = beakNode.greater;
}
beakNode.lesser = multiNode;
multiNode.greaterParent = beakNode;
beakNode.greater = newMultiNode;
newMultiNode.lesserParent = beakNode;
newMultiNode.parent = multiNode.parent;
return beakNode;
}

protected MultiNode addNode(Node node,MultiNode multiNode) {
if(node == null) {
return multiNode;
}
else if(multiNode == null) {
root = multiNode = new MultiNode();
multiNode.tail = multiNode.head = node;
multiNode.nodeCounter++;
return multiNode;
}
else{
Node head = multiNode.head;
do{
if(head.num > node.num) {
if(head.lesser!=null) {
addNode(node,head.lesser);
}
else {
if(head.prev!=null) {
if(head.lesser != null) {
node.greater = head.lesser;
head.lesser.lesserParent = node;
}
if(head.prev.greater!=null) {
node.lesser = head.prev.greater;
head.prev.greater.greaterParent = node;
}
head.prev.next = node;
node.prev = head.prev;
}
else {
if(head.lesser!=null) {
node.greater = head.lesser;
head.lesser.lesserParent = node;
}
multiNode.head = node;
}
node.next = head;
head.prev = node;
if(++multiNode.nodeCounter == multiNums) {
Node breakNode = fissAndReturnBreakNode(multiNode,multiNums/2);
addNode(breakNode,multiNode.parent);
}
}
return multiNode;
}
}while((head = head.next)!=null);

if(multiNode.tail.greater!=null) {
addNode(node,multiNode.tail.greater);
}
else {
multiNode.tail.next = node;
node.prev = multiNode.tail;
if(multiNode.tail.greater!=null) {
node.lesser = multiNode.tail.greater;
multiNode.tail.greater.greaterParent = node;
}
multiNode.tail = node;
if(++multiNode.nodeCounter == multiNums) {
Node breakNode = fissAndReturnBreakNode(multiNode,multiNums/2);
addNode(breakNode,multiNode.parent);
}
}
return multiNode;
}
}

public static void main(String[] args) {
BTree tree = new BTree(4);//4叉B树每个multi最多含有3个节点
for(int i=0;i<7;i++)
tree.addNum(i);
BTree.printTree(tree.root);
}

}

请移步

个人主页: yangyitao.top

手撕AVL树

欢迎查看Eetal的第十五篇博客–手撕AVL树

BST

BINARY SEARCH TREE二叉查找树,代表树中节点为有序存储
所有节点都相同满足以下条件之一
1.左子节点值大于等于根节点值且右子节点值小于根节点值
2.左子节点值小于等于根节点值且右子节点值大于根节点值
的二叉树即位二叉查找树
AVL average BST 是一棵平衡的二叉查找树,通过平衡使得单次查找的时间复杂度不会超过logn

AVL Tree

平衡树的特点是,任意一个节点,其左子树的高度与右子树的高度差保持在1以内(包括1)
当出现高度差超过1的子树则进行旋转来调整,旋转时不破坏有序

旋转

右旋转

假设左边过高(一般这种过高表现为高度差为2,因为我们应在每次进行插入和删除时旋转进行维护,所以不会出现高度差为3的情况)
普通右旋
这时我们通过这种右旋转可以把左边过高子树的一个节点提上来,使得左子树节点高度-1,右子树节点高度+1
伪代码就是
parent = 要旋转的节点
将parent的左节点断开,再将断开的左节点的右子树断开,重新接到parent,作为parent新的左子树
将parent从他父亲断开,将parent作为他原来的左子节点(被断开的左子节点)的右子树
最后将parent原来的父亲作为此时parent新的父节点(原来的左子节点)的父亲(就是交换位置)

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public Node rightTransformAndReturn(Node node) {
System.out.println("right transform");
Node left = node.leftChild;
Node leftRightChild = left.rightChild;
if(leftRightChild!=null) {
leftRightChild.parent = node;
leftRightChild.status = LEFTSTATUS;
}
node.leftChild = leftRightChild;
left.rightChild = node;
left.parent = node.parent;
node.parent = left;
left.status = node.status;
if(node.status == LEFTSTATUS) {
left.parent.leftChild = left;
}
else if(node.status == RIGHTSTATUS){
left.parent.rightChild = left;
}
else {
root = left;
}
node.status = RIGHTSTATUS;
while(node!=null) {
node.height = Math.max(getHeight(node.leftChild)+1, getHeight(node.rightChild)+1);
node = node.parent;
}
node = left;
return node;
}

上面只分析了第一种情况,有时左子树会是这样
特殊右旋
这时如果按照第一种的做法,此时最终结果会使得右子树高于左子树导致不平衡
原因是第一步里有 这一步

将parent的左节点断开,再将断开的左节点的右子树断开,重新接到parent,作为parent新的左子树

这一步是为了保证其有序不被破坏,所以从断开的是其左节点的右子树,也就是这颗右子树高度是不变的
而后右子树又会链接到parent,如果导致树高的不平衡是这颗右子树造成,则旋转后其仍是不平衡的(因为左边减少的高度1实际是左子树的高度)

于是我们得想办法把他变回第一种情况,做法就是,先使用左旋转其parent的左子树,将其变为下图
特殊右旋
这时就可以使用第一种情况的方法来应对了
至于为什么只要一次右旋转因为一开始我们就讲过,高度差不会超过2,那平衡的子树的左右结点高度差就不会超过1

左旋转

此处就是右旋转的镜像实现,就不过多阐述

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public Node leftTransformAndReturn(Node node) {
System.out.println("left transform");
Node right = node.rightChild;
Node rightLeftChild = right.leftChild;
if(rightLeftChild!=null) {
rightLeftChild.parent = node;
rightLeftChild.status = RIGHTSTATUS;
}
node.rightChild = rightLeftChild;
right.leftChild = node;
right.parent = node.parent;
node.parent = right;
right.status = node.status;
if(node.status == LEFTSTATUS) {
right.parent.leftChild = right;
}
else if(node.status == RIGHTSTATUS){
right.parent.rightChild = right;
}
else {
root = right;
}
node.status = LEFTSTATUS;
while(node!=null) {
node.height = Math.max(getHeight(node.leftChild)+1, getHeight(node.rightChild)+1);
node = node.parent;
}
node = right;
return node;
}

最终代码

我们将上方阐述的特殊情况(左旋转的可以联想都是对应的)总结,把对于两种情况的处理都封装
使用递归实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198

public class AVLTree {

Node root;
static final int LEFTSTATUS = 0;
static final int RIGHTSTATUS = 1;

static class Node{
int num;
Node leftChild;
Node rightChild;
Node parent;
int height = 0;
int status = -1;//root
@Override
public String toString() {
return num+",height["+height+"]->left["+(leftChild==null?"null":leftChild.num)+"],->right["+(rightChild==null?"null":rightChild.num)+"]";
}

}
public void printTree(Node parent) {
if(parent!=null) {
System.out.println(parent);
printTree(parent.leftChild);
printTree(parent.rightChild);
}
}
//注意,左旋右旋会改变原来的父节点位置的节点(包括root)
public Node leftTransformAndReturn(Node node) {
System.out.println("left transform");
Node right = node.rightChild;
//双旋转
if(getHeight(right.leftChild)>getHeight(right.rightChild)) {
rightTransformAndReturn(right);
}
Node rightLeftChild = right.leftChild;
if(rightLeftChild!=null) {
rightLeftChild.parent = node;
rightLeftChild.status = RIGHTSTATUS;
}
node.rightChild = rightLeftChild;
right.leftChild = node;
right.parent = node.parent;
node.parent = right;
right.status = node.status;
if(node.status == LEFTSTATUS) {
right.parent.leftChild = right;
}
else if(node.status == RIGHTSTATUS){
right.parent.rightChild = right;
}
else {
root = right;
}
node.status = LEFTSTATUS;
while(node!=null) {
node.height = Math.max(getHeight(node.leftChild)+1, getHeight(node.rightChild)+1);
node = node.parent;
}
node = right;
return node;
}
public Node rightTransformAndReturn(Node node) {
System.out.println("right transform");
Node left = node.leftChild;
//双旋转
if(getHeight(left.rightChild)>getHeight(left.leftChild)) {
leftTransformAndReturn(left);
}
Node leftRightChild = left.rightChild;
if(leftRightChild!=null) {
leftRightChild.parent = node;
leftRightChild.status = LEFTSTATUS;
}
node.leftChild = leftRightChild;
left.rightChild = node;
left.parent = node.parent;
node.parent = left;
left.status = node.status;
if(node.status == LEFTSTATUS) {
left.parent.leftChild = left;
}
else if(node.status == RIGHTSTATUS){
left.parent.rightChild = left;
}
else {
root = left;
}
node.status = RIGHTSTATUS;
while(node!=null) {
node.height = Math.max(getHeight(node.leftChild)+1, getHeight(node.rightChild)+1);
node = node.parent;
}
node = left;
return node;
}

public int getHeight(Node node) {
//获得树高,null为-1
return node == null ? -1 : node.height;
}
public int getBF(Node node) {
//获得平衡因子---左子树高-右子树高
return getHeight(node.leftChild)- getHeight(node.rightChild);
}
public void avl(Node parent) {
if(parent == null) {
return;
}
else {
Node left = parent.leftChild;
Node right = parent.rightChild;
if(getHeight(right)>getHeight(left)+1) {
leftTransformAndReturn(parent);
}
else if(getHeight(left)>getHeight(right)+1) {
rightTransformAndReturn(parent);
}
else {
avl(parent.parent);
}
}
}
public Node addAndReturnParent(Node node,Node parent) {
if(parent == null) {
root = parent = node;
}
else {
if(parent.num>node.num) {
//right add
Node right = parent.rightChild;
if(right == null) {
node.parent = parent;
node.status = RIGHTSTATUS;
parent.rightChild = node;
if(parent.leftChild == null) {
Node temp = parent;
temp.height++;
boolean flag = true;
while((temp=temp.parent)!=null && flag) {
int oldHeight = temp.height;
temp.height = Math.max(getHeight(temp.leftChild)+1, getHeight(temp.rightChild)+1);
flag = (oldHeight != temp.height);
}
avl(parent);
}
}
else {
addAndReturnParent(node,right);
}
}
else {
//left add
Node left = parent.leftChild;
if(left == null) {
node.parent = parent;
parent.leftChild = node;
node.status = LEFTSTATUS;
if(parent.rightChild == null) {
Node temp = parent;
temp.height++;
boolean flag = true;
while((temp=temp.parent)!=null && flag) {
int oldHeight = temp.height;
temp.height = Math.max(getHeight(temp.leftChild)+1, getHeight(temp.rightChild)+1);
flag = (oldHeight != temp.height);
}
avl(parent);
}
}
else {
addAndReturnParent(node,left);
}
}
}
return parent;
}
public Node addNum(int num) {
Node node = new Node();
node.num = num;
addAndReturnParent(node, root);
return node;
}

public Node findNum(int num) {
Node node = root;
while(node!=null) {
if(node.num == num)
break;
else if(node.num<num)
node = node.leftChild;
else
node = node.rightChild;
}
return node;
}

}

节点删除

如果要删除节点,此时第一考虑树高,
1.如果是叶子节点我们可以直接删除,并调整树
2.不是叶子节点,找出一个作为替代的叶子节点,顶替其位置(只需要交换值),把问题变为第一种情况

具体的顶替查找就是,如果其左子树为空则左旋一次,变为叶子节点
如果右子树为空则右旋一次,变为叶子结点
如果都不为空,根据左右子树高度判断,
从高度大的一方查找替代后能保持有序的叶子节点(此处判断主要是为了优化减少旋转的次数,不判断选择一边也不影响,因为最终都会调整)
最后删除叶子节点后递归调整树

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
public Node deleteNode(Node node) {
System.out.println("delete Node...:"+node);
if(node == null)
return node;
if(getHeight(node)==0) {
Node parent = node.parent;
node.parent = null;
if(node == root) {
root = null;
}
else if(node.status == LEFTSTATUS) {
parent.leftChild = null;
Node temp = parent;
boolean flag = true;
while((temp=temp.parent)!=null && flag) {
int oldHeight = temp.height;
temp.height = Math.max(getHeight(temp.leftChild)+1, getHeight(temp.rightChild)+1);
flag = (oldHeight != temp.height);
}
avl(parent);

}
else {
parent.rightChild = null;
Node temp = parent;
boolean flag = true;
while((temp=temp.parent)!=null && flag) {
int oldHeight = temp.height;
temp.height = Math.max(getHeight(temp.leftChild)+1, getHeight(temp.rightChild)+1);
flag = (oldHeight != temp.height);
}
avl(parent);
}

}
else {
if(node.leftChild == null) {
leftTransformAndReturn(node);
deleteNode(node);
}
else if(node.rightChild == null) {
rightTransformAndReturn(node);
deleteNode(node);
}
else if(getHeight(node.leftChild)>getHeight(node.rightChild)) {
Node leftSmall = node.leftChild;
while(leftSmall.rightChild!=null) {
leftSmall = leftSmall.rightChild;
}
node.num ^= leftSmall.num;
leftSmall.num ^= node.num;
node.num ^= leftSmall.num;
deleteNode(leftSmall);
}
else {
Node rightBig = node.rightChild;
while(rightBig.leftChild!=null) {
rightBig = rightBig.leftChild;
}
node.num ^= rightBig.num;
rightBig.num ^= node.num;
node.num ^= rightBig.num;
deleteNode(rightBig);
}
}
return node;
}
public Node deleteNum(int num) {
Node node = findNum(num);
return deleteNode(node);
}

请移步

个人主页: yangyitao.top

java的四种引用类型

欢迎查看Eetal的第十四篇博客–java的四种引用类型

引用队列

ReferenceQueue,引用队列内部维护一个Lock锁,除了强引用,其他类型引用都包含一个可以传入引用队列的构造函数
引用的对象在即将被回收时,会把绑定了引用队列的引用加入引用队列
然后回收该对象,因为这时其会变为InActive(死亡 )

Reference抽象类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
   /*引用实例处于四种可能的内部状态之一:
*
*活性:由垃圾收集器进行特殊处理。一些
*收集器检测到referent已更改为适当的状态,
*它将更改实例的状态为挂起或不活动,具体取决于实例是否在队列中注册已创建。
*在前一种情况下,它还将实例添加到挂起的引用列表。
*新创建的实例处于活动状态。
*
*挂起:挂起引用列表的元素,等待被引用处理程序线程排队。
*未注册的实例从未处于这种状态。
*
*排队:处于等待加入引用队列的等待队列,(因为加入引用队列的方法需要获取锁)
*
*不活动:没什么可做的。一旦一个实例变为非活动状态,状态永远不会改变。
*
*状态在队列和下一个字段中编码如下:
*
*active:queue=referencequeue,注册实例,
*或referencequeue.null,如果它未注册到队列;
*next=NULL。
*
*挂起:queue=referencequeue,实例已注册;
*next=this
*
*排队:queue=referencequeue.enqueued;
*next=following instance
*在队列中,或者在列表的末尾。
*
*非活动:queue=referencequeue.null;
*next=this。
*
*使用此方案,收集器只需按顺序检查下一个字段确定引用实例是否需要特殊处理:如果下一个字段为空,则实例处于活动状态;
*如果为非空,收集器应该正常处理该实例。
*确保并发收集器可以发现活动引用不干扰可能应用的应用程序线程的对象enqueue()方法到这些对象,
*收集器应链接通过发现的字段发现的对象。发现的字段还用于链接挂起列表中的引用对象。
*/

Reference(T referent, ReferenceQueue<? super T> queue) {
this.referent = referent;
this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
}
private T referent; /* Treated specially by GC */

volatile ReferenceQueue<? super T> queue;

网上大部分博客对于四种引用的说法都不准确,不去关心为何垃圾回收时会将对应引用加入对应引用队列
这里留意抽象类里的注释Treated specially by GC(被gc特殊对待)
具体过程就是,当满足了该引用对象被回收的条件时(在没有强引用引用他的前提下)
将该引用加入引用队列,并且该引用队列不再引用该对象(清除,注意虚引用在此处不同,虚引用需要等到对象上所有的引用清除以后才清除)
同时该对象也会变为死亡状态,会被垃圾回收器回收
接下来讲讲各个引用的条件

强引用

没什么好说的
java默认的引用类型,就是强引用类型
比如
Object o;
这就是一个强引用,只是没有引用对象

软引用

被软引用的对象在垃圾回收器线程扫描到且当jvm内存不足时,该软引用就会不再引用该对象
软引用可以与引用队列搭配使用
使用方式(传递对象)

SoftReference sr = new SoftReference(new Object());
sr.get(); //返回软引用对象,此处为Object类型

弱引用

被弱引用的对象在垃圾回收器线程扫描到时,该弱引用就会不再引用该对象
弱引用可以与引用队列搭配使用

WeakReference wr = new WeakReference(new Object());
wr.get(); //返回弱引用对象,此处为Object类型

虚引用

虚引用并不影响对象的生命周期
虚引用必须与引用队列搭配使用
虚引用并不可访问引用对象,get总是返回null
虚引用需要等到对象上所有的引用清除以后才清除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class PhantomReference<T> extends Reference<T> {

/**
* Returns this reference object's referent. Because the referent of a
* phantom reference is always inaccessible, this method always returns
* <code>null</code>.
*
* @return <code>null</code>
*/
public T get() {
return null;
}
.....
}

ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (new Object(), queue);
pr.get();//返回null

验证代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class RefferenceDemo {
static ReferenceQueue<Object> rq = new ReferenceQueue<Object>();
static Reference wr;
public static void test() {
wr = new WeakReference(new Object(),rq);
System.out.println(wr.get().hashCode());

}
public static void main(String[] args) {
test();
//Object o = wr.get(); //解除注释会增加强引用,则调用gc也不会回收,引用队列poll为null
//注释没有强引用,则调用gc会回收,引用队列poll为不为null
System.gc();
System.out.println(wr.get());
wr = rq.poll();
System.out.println("poll:"+wr+",getValue:"+wr.get());
System.out.println("poll:"+rq.poll());
System.out.println(wr.get());
}
}

输出

1
2
3
4
5
118352462
null
poll:java.lang.ref.WeakReference@5c647e05,getValue:null
poll:null
null

请移步

个人主页: yangyitao.top

GC与垃圾回收分代

欢迎查看Eetal的第十三篇博客–GC与垃圾回收分代

gc与finalize

网上很多博客简略的说确保对象死活需要经过两次标记这是错误的。
Object有个finalize,默认是空实现
被回收时会判断这个对象是否重写了finalize方法,如果没有就直接回收
否则会把该对象加入一个f队列(代表需要执行回收方法的队列)
jvm会分配一个执行finalize的线程去执行这个队列每个对象的finalize方法,如果在finalize方法中对象吧自己的this给不在finalize队列中的对象引用,则将其从finalize队列移除
jvm并不保证执行finalize的线程绝对会执行结束,而是超过一个固定时间以后就终止(防止死锁)。
finalize线程终止后,其中还保留的对象都会被回收

判断对象是否存活

以前采用过标记法,即每个对象加一个计数器,被引用了+1,取消引用-1.当为0代表可以回收
此种方法会导致无法处理循环引用(A引用B,B引用A且而GCroot没有任何对象引用A和B,则A和B永远不会被回收),因此被遗弃了
现在普遍使用GCroot扫描方法

GCRoot

以下对象可以作为GCroot
Class - 由系统类加载器(system class loader)加载的对象,这些类是不能够被回收的,他们可以以静态字段的方式保存持有其它对象。
//其他类加载器加载的对象,jvm默认不回收,除非进行特殊指定
Thread - 活着的线程
Stack Local - Java方法栈的local变量或参数
JNI Local - JNI方法的local变量或参数
JNI Global - 全局JNI引用
Monitor Used - 用于同步的监控对象(锁)
Held by JVM - 用于JVM特殊目的由GC保留的对象,但实际上这个与JVM的实现是有关的。可能已知的一些类型是:系统类加载器、一些JVM知道的重要的异常类、一些用于处理异常的预分配对象以及一些自定义的类加载器等。

扫描过程:
从GCroot开始扫描,如果可达的对象则为存活
即为一个连通图的判断,不在图里的其他元素默认为死亡

分代回收

jdk1.7以前
java把堆内存分为两块,新生代和老生代
还有一块保存静态变量,常量和Class对象的永生代保存在方法区

新生代采用复制算法
老生代使用标记整理算法
永生代一般不回收,只有当需要加载Class而内存不够才会触发fullgc进行回收,会和老年代一起回收(标记整理)

新生代又分为 一块eden 和 两块survivor

参数: 指定新生代大小 -XX:NewSize
指定老年代与新生代大小比例 -XX:NewRatio
指定新生代中伊甸园和存活地大小比例: -XX:SurvivorRatio

刚开始new出来的对象都在伊甸园(eden)
然后一块survivor标记为存活地

复制算法

触发minor gc以后,会把另一块没有标记的空的survivor标记为存活地
会把eden中存活的对象和旧存活地的还活着的对象放进这块新的存活地,然后把旧存活地和伊甸园清空
当然如果存活地大小不足以容纳时,多余的对象会直接进入老年代
对象第一次进入survivor时,标记年龄为1
每次在survivor之间转移时年龄会增长,达到某个值时,就不进入新的存活地而是直接进入老年代
这个年龄可以通过jvm参数设置:-XX:MaxTenuringThreshold

标记整理算法

首先会遍历所有对象,标记还存活的对象,并清理掉死亡的对象,然后将存活的对象整理(挨到一起)
整理的目的和磁盘碎片压缩的道理一样,整理后才能留出更大的空间留给大对象,避免出现大量碎片
因为标记整理算法相对复制算法更慢,但是不需要额外的备用空间,
而minor gc触发频繁,老年代的gc触发较少,但是可能会累计较大空间
因此minor gc使用复制算法,major gc使用标记整理算法

对新生代触发的gc称为minor gc,而对老生代触发的gc称为major gc
fullgc是指触发所有的回收(包括对永久代的回收)

垃圾回收器

新生代

Serial串行回收器

基于单线程的回收器,在运行时会导致Stop The World,但是效率高,使用的算法就是复制算法
可以和CMS搭配使用

ParNew回收器

Parallel New Generation
Serial回收器的多线程版本,因为是多线程版本,所以不用触发Stop the world
但是效率不太好,因为需要花开销在线程的切换调度销毁上
当然在多cpu电脑上会更有优势
可以和CMS搭配使用

Parallel Scavenge回收器

与ParNew类似,都是复制算法的并行收集器
但是Parallel Scavenge的目标是达到一个可控的吞吐量
吞吐量=程序运行时间/(程序运行时间+GC时间)
Parallel Scavenge提供了两个参数用以精确控制吞吐量
分别是用以控制最大GC停顿时间的-XX:MaxGCPauseMillis及直接控制吞吐量的参数-XX:GCTimeRatio.
同时可以设置垃圾自适应调配策略,由虚拟机自动调优
由于是Parallel Scavenge没有使用原本HotSpot其它GC通用的那个GC框架,所以不能跟使用了那个框架的CMS搭配使用。

老年代

SerialOld回收器

Serial的老年代版本,还是单线程也会触发Stop the world,不过使用的是标记整理算法

ParallelOld回收器

Parallel Scavenge的年老代版,使用标记整理算法,并行

CMS回收器

Concurrent Mark Sweep,并发回收器,旨在减少STW时间(通过两次标记,第二次只查看dirty的)
在G1出现前的主流一般server版本jvm就是ParNew+CMS
过程
初始标记:该阶段只标记GC Root节点直接引用的老年代节点,会造成STW,但是时间很短。(因为只标记被直接引用的)
并发标记:GC ROOT TRACING。遍历老年代,然后标记所有存活的对象(包括不是gcRoot直接引用的),它会根据上个阶段找到的 GC Roots 遍历查找
在并发标记阶段被修改的节点(新降临到老年代的对象以及在并发阶段被修改了的对象)的对象会被标记为dirty,加入Dirty队列
重新标记:stop the world
遍历DirtyCard,更新标记被修改的老年代对象
停顿时间比初始标记稍长,但远比并发标记短
并发清理:遍历老年代清理死亡对象,整理空间

请移步

个人主页: yangyitao.top

并发基石-AQS与Condition

欢迎查看Eetal的第十二篇博客–并发基石-AQS与Condition

Condition

在前面的博客JUC相关中,已经初略介绍了Condition接口,代表一个条件,可以阻塞、唤醒一个条件队列
今天来具体讲讲其与AQS(AbstractQueueSynchronizer)的实现源码
Condition中的加入条件队列,阻塞,唤醒等等操作都是由AbstractQueueSynchronizer或其子类方法完成

AQS

AbstractQueueSynchronizer是java提供的队列同步器,个人理解更贴近一个线程调度的管理者
首先我们了解其内部类Node,Node既可以代表Condition的一个条件队列(单向链表),也可以代表当前锁资源的同步队列(双向链表)中的一个竞争线程的节点
先看其结构

Node

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
static final Node EXCLUSIVE = null;

/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
static final int PROPAGATE = -3;

/**
* Status field, taking on only the values:
* SIGNAL: The successor of this node is (or will soon be)
* blocked (via park), so the current node must
* unpark its successor when it releases or
* cancels. To avoid races, acquire methods must
* first indicate they need a signal,
* then retry the atomic acquire, and then,
* on failure, block.
* CANCELLED: This node is cancelled due to timeout or interrupt.
* Nodes never leave this state. In particular,
* a thread with cancelled node never again blocks.
* CONDITION: This node is currently on a condition queue.
* It will not be used as a sync queue node
* until transferred, at which time the status
* will be set to 0. (Use of this value here has
* nothing to do with the other uses of the
* field, but simplifies mechanics.)
* PROPAGATE: A releaseShared should be propagated to other
* nodes. This is set (for head node only) in
* doReleaseShared to ensure propagation
* continues, even if other operations have
* since intervened.
* 0: None of the above
*
* The values are arranged numerically to simplify use.
* Non-negative values mean that a node doesn't need to
* signal. So, most code doesn't need to check for particular
* values, just for sign.
*
* The field is initialized to 0 for normal sync nodes, and
* CONDITION for condition nodes. It is modified using CAS
* (or when possible, unconditional volatile writes).
*/
volatile int waitStatus;

/**
* Link to predecessor node that current node/thread relies on
* for checking waitStatus. Assigned during enqueuing, and nulled
* out (for sake of GC) only upon dequeuing. Also, upon
* cancellation of a predecessor, we short-circuit while
* finding a non-cancelled one, which will always exist
* because the head node is never cancelled: A node becomes
* head only as a result of successful acquire. A
* cancelled thread never succeeds in acquiring, and a thread only
* cancels itself, not any other node.
*/
volatile Node prev;

/**
* Link to the successor node that the current node/thread
* unparks upon release. Assigned during enqueuing, adjusted
* when bypassing cancelled predecessors, and nulled out (for
* sake of GC) when dequeued. The enq operation does not
* assign next field of a predecessor until after attachment,
* so seeing a null next field does not necessarily mean that
* node is at end of queue. However, if a next field appears
* to be null, we can scan prev's from the tail to
* double-check. The next field of cancelled nodes is set to
* point to the node itself instead of null, to make life
* easier for isOnSyncQueue.
*/
volatile Node next;

/**
* The thread that enqueued this node. Initialized on
* construction and nulled out after use.
*/
volatile Thread thread;

/**
* Link to next node waiting on condition, or the special
* value SHARED. Because condition queues are accessed only
* when holding in exclusive mode, we just need a simple
* linked queue to hold nodes while they are waiting on
* conditions. They are then transferred to the queue to
* re-acquire. And because conditions can only be exclusive,
* we save a field by using special value to indicate shared
* mode.
*/
Node nextWaiter;

/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
...
}

Node中保存有竞争锁的线程对象
prev属性代表同步队列中的上一个节点
next属性代表同步队列中的下一个节点
nextWaiter属性,如果Node处于条件队列中,则nextWaiter指向条件队列中的下一个Node
nextWaiter属性,如果Node处于同步队列中,则nextWaiter代表同步模式,SHARE和EXCLUSIVE
因为条件队列中的都是互斥,所以不需要保存该mode

执行开始

首先Condition调用了await方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}

简单捋一下await的逻辑,首先创建一个Node到等待队列里,然后释放资源
接着不断循环判断是否已经在同步队列里了,不在则挂起线程直到被UnPark
当处于同步队列里且被唤醒后就会调用请求资源的方法
不断的自旋尝试获取锁,如果处于头节点则获取得到资源,恢复执行后续代码
如果他不是同步队列的头节点,则判断他在同步队列里的前驱节点
如果前驱节点也处于请求释放的状态,因为总是唤醒前面的节点,所以这个节点这时候就可以安全的挂起
如果前驱节点处于取消状态,则不断清理被前驱取消的节点,直到第一个处于非取消状态的节点,将该节点作为当前结点的前驱
剩下的waitstatus中-2代表在条件队列已经不需要考虑,只剩下0(初始状态需要请求)和-3(读写锁的读锁请求到共享资源时,标记为该状态代表可以执行)
就把前驱节点更改为请求释放signal的状态
最后把所有已经不是条件等待状态的node移出队列

如果调用了signal方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
public final void signal() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;

/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);

/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}

默认唤醒条件队列从头节点开始往后找到的第一个处于非取消状态的节点
把其加入同步队列,并更改状态
如果更改失败说明这其间该节点被取消了,则返回失败继续往后寻找节点唤醒
如果更改成功,则判断同步队列的前驱节点的状态,如果该节点被取消了则唤醒当前新加入的线程53
原因是进入同步队列时,只有作为头节点才会立即请求资源
否则,同步队列里只有释放资源时才会再次Unpark同步队列下一个线程
因此防止前面的节点未获取到资源就取消导致后续没有调起下一个Unpark
最好最安全的方法是如果前驱节点突然取消了,unPark一下
这样即使还有别的线程在占用资源,他也会在获取资源失败后再次进入park

AQS中的实现

添加节点,默认添加到尾部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}

条件队列的移除上面已经分析了,那同步队列的呢?

现在我们回到一开始await被signal唤醒后自旋请求资源的地方来看请求资源的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void cancelAcquire(Node node) {
// Ignore if node doesn't exist
if (node == null)
return;

node.thread = null;

// Skip cancelled predecessors
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;

// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;

// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;

// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}

node.next = node; // help GC
}
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
//---默认Sync使用不公平,此处作为示例
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}

当请求资源时什么情况执行finally
1.前驱节点为null,空指针异常failed变量为初始值true,此时取消资源会把自己移除出同步队列(因为头指针是特殊标记,不代表实际请求资源的Node)
—在插入同步队列时,如果没有头指针会自动初始化一个头指针,所以第一个插入的节点不会有问题
2.该节点的前驱节点是头节点,则当前结点获取到资源,并把自己设置为头指针,failed变量标记为false(因为新的头指针不需要删除,这样就把旧的头节点出队了)
3.超出资源限制的请求数量,throw new Error(“Maximum lock count exceeded”),取消该node
cancelAcquire方法会将当前结点标记为取消状态,并且迭代移除当前结点的已取消的前驱节点知道第一个非取消状态的节点
主要操作是
1.当前是尾节点,删除当前结点更新尾节点
2.前驱节点不是头节点(因为头节点是标记节点要特殊处理),删除当前结点
3.前驱节点是头节点,调用unParkSuccess去唤起后一个线程(因为他没有获取到资源又是第一个节点不执行release)

而被标记为取消的节点在shouldParkAfterFailedAcquire中会被删除
因为Park下一个节点,他不是head下的第一个节点所以他会请求失败并执行shouldParkAfterFailedAcquire

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}

请移步

个人主页: yangyitao.top

并发基石-Markword与锁升级

欢迎查看Eetal的第十一篇博客–并发基石-Markword与锁升级

synchronized

synchronized关键字是java提供的互斥锁关键字,我们常说的互斥锁一般都是非自旋锁,即竞争不到锁的线程会进入阻塞状态知道被唤醒
今天我们来讲讲java中用来对synchronized进行优化的三种锁,同时会介绍markword对象头
目前我在网上搜到的十几篇博客讲的都有问题,可能有写对的我没搜到.
很多人不经过验证直接把markOop.hpp中的JavaThread*当成ThreadId这是错误的,实际是java线程在C语言的指针
并且未计算过hashCode和计算过hashCode的情况也是不一样的
本篇博客最后会展示使用jol工具,读取展示对象头的结果进行验证
附上openjdk的markOop.hpp链接

对象头Markword

对象头是java将对象比较常用和重要的运行时数据,如hashCode、gc标识、存活年龄等等进行集中存储的一组数据
占据8个字节,以下只讨论目前比较常见的64位的情况

偏向锁、轻量级锁、重量级锁介绍

64bit下各锁状态的Markword格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
64 bits:
--------
unused:25 hash:31 -->| unused:1 age:4 biased_lock:1 lock:2 (normal object)
JavaThread*:54 epoch:2 unused:1 age:4 biased_lock:1 lock:2 (biased object)
PromotedObject*:61 --------------------->| promo_bits:3 ----->| (CMS promoted object)
size:64 ----------------------------------------------------->| (CMS free block)

unused:25 hash:31 -->| cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && normal object)
JavaThread*:54 epoch:2 cms_free:1 age:4 biased_lock:1 lock:2 (COOPs && biased object)
narrowOop:32 unused:24 cms_free:1 unused:4 promo_bits:3 ----->| (COOPs && CMS promoted object)
unused:21 size:35 -->| cms_free:1 unused:7 ------------------>| (COOPs && CMS free block)
[ptr | 00] locked ptr points to real header on stack
[header | 0 | 01] unlocked regular object header
[ptr | 10] monitor inflated lock (header is wapped out)
[ptr | 11] marked used by markSweep to mark an object

32bit下各锁状态的Markword格式

1
2
3
4
5
6
32 bits:
--------
hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
size:32 ------------------------------------------>| (CMS free block)
PromotedObject*:29 ---------->| promo_bits:3 ----->| (CMS promoted object)

也就是对象头的最后两位,是作为锁的状态标志
00—轻量锁
01—偏向锁/无锁
10—重量锁
11—GC标记
偏向锁状态和无锁状态通过区分对象头倒数第三位来确定,0代表无锁,1代表偏向锁
注意,未计算hashCode的对象的初始状态为匿名偏向锁(线程指针为0,代表无线程获取)而非无锁

偏向锁与无锁

java中对于锁的实际获取依赖于UnSafe调用native方法去实现不同操作系统上的cas原语操作
如果一个对象,在每次作业的运行始终处于单一线程,那每次对于锁的检测、获取和释放都会对性能造成不小的消耗
于是java引入了偏向锁
当一个处于匿名偏向锁状态的对象,第一次被一个线程竞争时,其对象头会被标记为偏向锁,同时存储其线程指针
接下来每次该线程对该锁的获取都不需要经过不必要的cas判断锁资源从而优化了性能,这也是偏向的由来

匿名偏向锁状态的对象如果计算了hashCode,则会变为无锁状态。hashCode存在markword,并且接下来不会再进去偏向锁

匿名偏向锁状态的对象被获取时,进入非匿名偏向锁状态,markword存储持有者的java线程在操作系统的C语言指针

无锁状态下的对象被获取时,会直接跳到轻量级锁(因为偏向锁下markword没有记录hashCode,没办法存储hashCode,而轻量级锁的下面讲)

非匿名偏向锁状态的对象计算了hashCode以后,会直接进入重量级锁,此时的重量级锁会(重量级锁的下面讲)

开启偏向锁的支持需要添加两个虚拟机参数

1
2
-XX:+UseBiasedLocking
-XX:BiasedLockingStartupDelay=0

第一个参数是开启偏向锁
第二个参数是指定立即开启,因为默认偏向锁的开启时在虚拟机运行后延时5秒
如果没有线程竞争,非匿名偏向锁偏向锁释放后会变回匿名偏向锁状态

锁升级-轻量级锁

当一个对象处于非匿名偏向锁状态下,如果有别的线程过来竞争,另一个线程尝试竞争锁,竞争失败并给予jvm一个竞争的信号以后进入自旋(不断尝试获取锁)
接下来在持有该锁的线程执行来到安全点时,会触发stop the world并将膨胀为轻量级锁
轻量级锁会创建一份锁记录(Lock Record)在当前持有他的线程的线程栈里
LockRecord中包含一个owner属性指向锁对象,而锁对象的markword中也会保存一个执行该LockRecord的指针

这时的竞争就是轻量级锁的竞争了,轻量级锁的竞争时,竞争锁的线程会在一个周期时间内不断的自旋获取锁,如果获取失败就会进入阻塞并将markword的锁标记标记为10(重量级锁)

因为LockRecord复制了markword,所以在执行同步块时并不去关注markword,只有到了释放时

1.锁对象的markword升级为了重量级锁,将锁对象升级为重量级锁,锁对象的markword存储一个指向一个由操作系统实现的mutex互斥变量,唤醒阻塞的竞争线程

2.markword没变化,释放锁,对象锁恢复到无锁状态(如果LockRecord记录的是偏向锁,则恢复到匿名偏向锁,否则恢复到无锁状态)

锁升级-重量级锁

当对象来到重量级锁以后,新被从竞争队列挑选出来一部分竞争锁的线程队列会一起竞争锁
最终竞争到锁的一个线程会继续运行,竞争失败的线程进入阻塞队列
处于执行状态的线程执行完同步代码块后,会释放锁并唤醒阻塞队列中的线程
将他们加入新的挑选出来的竞争锁的线程队列,并重新竞争锁,重复以上操作
因为需要阻塞和唤醒线程,所以需要从用户态到系统态切换,所以重量级锁下的系统开销很大

代码验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Thread currentThread = Thread.currentThread();
System.out.println("threadId : "+Long.toBinaryString(currentThread.getId()));
Object o = new Object();
System.out.println("-------------------------------------------------------------------------------------------------------");
System.out.println("init object info");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");
synchronized (o) {
System.out.println("synchronized lock object info");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("synchronized finished");
System.out.println("-------------------------------------------------------------------------------------------------------");
}
Thread.sleep(2000);
System.out.println("after synchronized object info");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("binary hashCode : "+Integer.toBinaryString(o.hashCode()));
System.out.println("-------------------------------------------------------------------------------------------------------");

System.out.println("after calculate hashcode object info");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");

synchronized (o) {
System.out.println("synchronized lock object info");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("synchronized finished");
System.out.println("-------------------------------------------------------------------------------------------------------");
}
Object o2 = new Object();
System.out.println("o2 hashCode : "+Long.toBinaryString(o2.hashCode()));
System.out.println("init lock object2 info");
System.out.println(ClassLayout.parseInstance(o2).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");
synchronized (o2) {
System.out.println("synchronized lock object2 info");
System.out.println(ClassLayout.parseInstance(o2).toPrintable());
System.out.println("synchronized finished");
System.out.println("-------------------------------------------------------------------------------------------------------");
}
System.out.println("after lock object2 info");
System.out.println(ClassLayout.parseInstance(o2).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");
//计算过hashcode的会直接进入轻量锁

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
threadId : 1
-------------------------------------------------------------------------------------------------------
init object info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

-------------------------------------------------------------------------------------------------------
synchronized lock object info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e8 09 01 (00000101 11101000 00001001 00000001) (17426437)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

synchronized finished
-------------------------------------------------------------------------------------------------------
after synchronized object info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 e8 09 01 (00000101 11101000 00001001 00000001) (17426437)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

binary hashCode : 100000111110100010001111000001
-------------------------------------------------------------------------------------------------------
after calculate hashcode object info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 c1 23 fa (00000001 11000001 00100011 11111010) (-98320127)
4 4 (object header) 20 00 00 00 (00100000 00000000 00000000 00000000) (32)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

-------------------------------------------------------------------------------------------------------
synchronized lock object info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 50 f7 c4 02 (01010000 11110111 11000100 00000010) (46462800)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

synchronized finished
-------------------------------------------------------------------------------------------------------
o2 hashCode : 110101100000011100010111110011
init lock object2 info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 f3 c5 81 (00000001 11110011 11000101 10000001) (-2117733631)
4 4 (object header) 35 00 00 00 (00110101 00000000 00000000 00000000) (53)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

-------------------------------------------------------------------------------------------------------
synchronized lock object2 info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 50 f7 c4 02 (01010000 11110111 11000100 00000010) (46462800)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

synchronized finished
-------------------------------------------------------------------------------------------------------
after lock object2 info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 f3 c5 81 (00000001 11110011 11000101 10000001) (-2117733631)
4 4 (object header) 35 00 00 00 (00110101 00000000 00000000 00000000) (53)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

-------------------------------------------------------------------------------------------------------

多线程竞争

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
Thread currentThread = Thread.currentThread();
System.out.println("threadId : "+Long.toBinaryString(currentThread.getId()));
Object o = new Object();
System.out.println("-------------------------------------------------------------------------------------------------------");
System.out.println("init object info");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");

new Thread() {
public void run() {
synchronized (o) {
System.out.println("-------------------------------------------------------------------------------------------------------");
System.out.println("-------------------------------------------------------------------------------------------------------");
System.out.println("another thread id "+Long.toBinaryString(this.getId()));
System.out.println("synchronized lock object info");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("synchronized finished");
System.out.println("-------------------------------------------------------------------------------------------------------");
System.out.println("------------------------------------------------------------------------------------------------------");
}
}
}.start();

synchronized (o) {
System.out.println("synchronized lock object info");
System.out.println(ClassLayout.parseInstance(o).toPrintable());

//有锁未计算hashCode状态下计算hashCode

o.hashCode();
System.out.println("when synchronized calculate hashcode ");
System.out.println(ClassLayout.parseInstance(o).toPrintable());

System.out.println("synchronized finished");
System.out.println("-------------------------------------------------------------------------------------------------------");
}
Thread.sleep(2000);
System.out.println("after synchronized object info");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("binary hashCode : "+Integer.toBinaryString(o.hashCode()));
System.out.println("-------------------------------------------------------------------------------------------------------");

System.out.println("after calculate hashcode object info");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");

synchronized (o) {
System.out.println("synchronized lock object info");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
System.out.println("synchronized finished");
System.out.println("-------------------------------------------------------------------------------------------------------");
}
Object o2 = new Object();
System.out.println("o2 hashCode : "+Long.toBinaryString(o2.hashCode()));
System.out.println("init lock object2 info");
System.out.println(ClassLayout.parseInstance(o2).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");
synchronized (o2) {
System.out.println("synchronized lock object2 info");
System.out.println(ClassLayout.parseInstance(o2).toPrintable());
System.out.println("synchronized finished");
System.out.println("-------------------------------------------------------------------------------------------------------");
}
System.out.println("after lock object2 info");
System.out.println(ClassLayout.parseInstance(o2).toPrintable());
System.out.println("-------------------------------------------------------------------------------------------------------");
//计算过hashcode的会直接进入轻量锁

输出结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
threadId : 1
-------------------------------------------------------------------------------------------------------
init object info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

-------------------------------------------------------------------------------------------------------
synchronized lock object info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ba c4 c4 02 (10111010 11000100 11000100 00000010) (46449850)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

when synchronized calculate hashcode
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ba c4 c4 02 (10111010 11000100 11000100 00000010) (46449850)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

synchronized finished
-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------
another thread id 1010
synchronized lock object info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) ba c4 c4 02 (10111010 11000100 11000100 00000010) (46449850)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

synchronized finished
-------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
after synchronized object info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 c1 23 fa (00000001 11000001 00100011 11111010) (-98320127)
4 4 (object header) 20 00 00 00 (00100000 00000000 00000000 00000000) (32)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

binary hashCode : 100000111110100010001111000001
-------------------------------------------------------------------------------------------------------
after calculate hashcode object info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 c1 23 fa (00000001 11000001 00100011 11111010) (-98320127)
4 4 (object header) 20 00 00 00 (00100000 00000000 00000000 00000000) (32)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

-------------------------------------------------------------------------------------------------------
synchronized lock object info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 90 f5 ad 02 (10010000 11110101 10101101 00000010) (44955024)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

synchronized finished
-------------------------------------------------------------------------------------------------------
o2 hashCode : 110101100000011100010111110011
init lock object2 info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 f3 c5 81 (00000001 11110011 11000101 10000001) (-2117733631)
4 4 (object header) 35 00 00 00 (00110101 00000000 00000000 00000000) (53)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

-------------------------------------------------------------------------------------------------------
synchronized lock object2 info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 90 f5 ad 02 (10010000 11110101 10101101 00000010) (44955024)
4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

synchronized finished
-------------------------------------------------------------------------------------------------------
after lock object2 info
java.lang.Object object internals:
OFFSET SIZE TYPE DESCRIPTION VALUE
0 4 (object header) 01 f3 c5 81 (00000001 11110011 11000101 10000001) (-2117733631)
4 4 (object header) 35 00 00 00 (00110101 00000000 00000000 00000000) (53)
8 4 (object header) e5 01 00 20 (11100101 00000001 00000000 00100000) (536871397)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

-------------------------------------------------------------------------------------------------------

请移步

个人主页: yangyitao.top

HashMap、TreeMap、HashTable、ConcurrentHashMap对比(jdk1.8)

欢迎查看Eetal的第十篇博客–HashMap、TreeMap、HashTable、ConcurrentHashMap对比(jdk1.8)

HashMap

HashMap是java中经常用来存取键值对形式的一个集合类
1.8以后实现方式为 数组(Node<K,V>[])+链表(Node)
首先计算出要存储的key应该到数组中哪个位置的链表下

1
2
3
4
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

静态方法hash将key的hashCode的高16位与低16位异或的值作为其hash值,异或是为了减少在map的hash数组长度较小时的hash碰撞,使其更均匀
如果key是null则为0,这也是为什么HashMap可以使用null作为key而ConcurrentHashMap不行的原因
在putVal方法中使用这个hash的值去与当前的数组大小求余计算出其位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) //---计算数组位置
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

重要参数

1
2
3
4
5
6
7
8
9
10
11
12
13
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

/**
* The maximum capacity, used if a higher value is implicitly specified
* by either of the constructors with arguments.
* MUST be a power of two <= 1<<30.
*/
static final int MAXIMUM_CAPACITY = 1 << 30;

/**
* The load factor used when none specified in constructor.
*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;

重要构造函数

hashmap限制容量CAPACITY为2的幂,每次扩容为乘以2,这样可以保证数据的均匀分布,同时使得hashmap只有resize方法而不需要rehash,下面详谈
第一个DEFAULT_INITIAL_CAPACITY默认的数组大小,也就是不指定时默认开辟的hash数组大小为2的4次方
第二个MAXIMUM_CAPACITY,最大扩容至的hash数组大小,如果指定的数组大小大于这个值,会被替换为这个值,原因是,数组的下表要求是int类型,而int在java中是4个字节,共32位,而第一位表示符号位,因此可以表示的最大2的幂为2的30次方
最后DEFAULT_LOAD_FACTOR代表负载因子,当put操作以后 map的size > hash数组的长度*负载因子时,就会触发扩容,
当然hashmap提供了指定initialCapacity的构造函数,但在1.8里,这个参数并不会改变其capacity,只会改变threshold的值以下为源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* Constructs an empty <tt>HashMap</tt> with the specified initial
* capacity and load factor.
*
* @param initialCapacity the initial capacity
* @param loadFactor the load factor
* @throws IllegalArgumentException if the initial capacity is negative
* or the load factor is nonpositive
*/
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}

/**
* Returns a power of two size for the given target capacity.
*/
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}

指定initialCapacity只能改变扩容时机,而并不改变hash数组长度

put操作与resize

put操作较简单,计算到对应Node,为null则新建,不为null则根据该Node类型为TreeNode还是普通Node执行不同的插入操作
当put以后,如果size大于threshold扩容时机,则进行扩容(数组长度乘以2)
同时进行rehash,此处源码使用了一个快捷的办法,因为数组的长度必定为2的倍数
假设长度为2的x次方,则旧hash数组第i个位置上的链表的所有Node的hash都应该为 Nj(2的x次方)+i
因为新扩容的数组长度为原来的两倍(重点),则 长度为 2
(2的x次方),此时进行resize
hash%(2(2的x次方)) == (hash%(2的x次方))+(2的x次方)(hash&(2的x+1次方))
而hash%(2的x次方)为node所在旧hash数组下标i,oldCap为旧数组的长度,式子可写为
新数组的位置下标 newI = i + oldCap * (hash & oldCap)
官方实现代码如下,将旧数组每个位置上的链表拆分为高低两条新链表,分别代表hash & oldCap为0和为1的节点
再把这两条链表对应到新数组的位置 newTab[j] 和 newTab[oldCap + j]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// j from 0 to oldCap-1
Node<K,V> loHead = null, loTail = null;
Node<K,V> hiHead = null, hiTail = null;
Node<K,V> next;
do {
next = e.next;
if ((e.hash & oldCap) == 0) {
if (loTail == null)
loHead = e;
else
loTail.next = e;
loTail = e;
}
else {
if (hiTail == null)
hiHead = e;
else
hiTail.next = e;
hiTail = e;
}
} while ((e = next) != null);
if (loTail != null) {
loTail.next = null;
newTab[j] = loHead;
}
if (hiTail != null) {
hiTail.next = null;
newTab[j + oldCap] = hiHead;
}

HashMap死锁问题

因为HashMap中的put和remove都是不加对象锁的,也就是非线程安全的
且其table属性(hash数组的引用)也没有使用volatile修饰(修饰了也不能百分百解决并发问题,因此官方默认HashMap为非线程安全的map以提高其效率)
如果在resize的过程,另一个线程拿到的是oldTable,并对其进行如删除元素或新增元素
因为原有的单条链表hi拆分重新组建为高低两条链表,这期间对oldTable节点的插入和删除操作可能会导致把新构建的链表中的部分节点形成环(因为新链表的序列与oldTable原有不一样)
最终导致在get和put时,遍历链表时进入无限死循环
因此在多线程下应使用ConcurrentHashMap代替HashMap

TreeMap

TreeMap为有序的map,本质是一条链表
可以在构造是传入一个比较器,或者每个元素的类都有实现Comparable接口
HashMap的插入为计算到数组位置后,添加到对应Node链表末端
TreeMap为比较计算位置后插入对应链表位置
在未传入比较器的情况下,key不允许为null

简介ConcurrentHashMap和HashTable

HashTable为HashMap的线程安全版,其中会有线程安全问题的方法都采用使用this对象作为锁的方式来实现互斥
而ConcurrentHashMap1.5以前为使用分段锁,即双数组,原本存放Entry的地方改为存放segemant数组,代码1000多行
segemant对象继承了ReentrantLock,包含有一个与Entry数组的属性
插入和删除修改时,只锁被Hash到的segemant对象,再hash元素到segemant的Entry数组对应的位置,插入到对应位置链表中去
jdk1.5开始,Doug Lea 大牛改为和HashMap一样的Node结构,往类里注入了Unsafe对象
修改时只锁被hash到的对应的链表头指针Node,通过各种cas操作和synchronized检验和修改插入等操作
jdk1.5以后的ConcurrentHashMap的源码足足来到6312行,TQL,到处都是使用unsafe的cas和计算对象偏移来完成原语操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   // Unsafe mechanics
private static final sun.misc.Unsafe U;
private static final long SIZECTL;
private static final long TRANSFERINDEX;
private static final long BASECOUNT;
private static final long CELLSBUSY;
private static final long CELLVALUE;
private static final long ABASE;
private static final int ASHIFT;

static {
try {
U = sun.misc.Unsafe.getUnsafe();
...
}
...
}

因此效率上而言ConcurrentHashMap会优于HashTable

请移步

个人主页: yangyitao.top

使用future机制和对象锁实现SynchronizedExecutor

欢迎查看Eetal的第九篇博客–使用future机制和对象锁实现SynchronizedExecutor

future

future代表一个任务的预执行结果,通过get方法获取执行结果

1
2
3
public interface Future<V> {
V get() throws Exception;
}

callable

callable代表一个要执行的任务,执行方法call,执行完成返回一个值

1
2
3
public interface Callable<V> {
V call() throws Exception;
}

executor

executor为执行器,通过执行器来执行任务并得到预执行结果对象future

1
2
3
public interface Executor<V> {
Future<V> execute(Callable<V> callable);
}

SynchronizedExecutor

使用synchronized关键字实现的执行器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class SynchronizedExecutor<V> implements Executor<V> {

static class ExecutorThread<V> extends Thread{
public Object lock;
public V result;
public Callable<V> task;
public Exception e;
public boolean isDone = false;
public ExecutorThread(Callable<V> task,Object lock) {
this.lock = lock;
this.task = task;
}
@Override
public void run() {
try {
result = task.call();
} catch (Exception e) {
this.e = e;
}finally {
synchronized (lock) {
//需持有锁才能调用notify
this.isDone = true;
lock.notifyAll();
//此处的加锁只是为了获得锁
}
}

}

}

@Override
public Future<V> execute(Callable<V> callable) {
Object lock = new Object();
ExecutorThread<V> thread = new ExecutorThread<V>(callable,lock);
thread.start();
Future<V> future = new Future<V>() {

@Override
public V get() throws Exception {
synchronized (lock) {
//通过锁机制,使得未完成时,get方法陷入等待队列,让出cpu给别的线程,异步完成时再唤醒
//需持有锁才能调用wait
while(!thread.isDone) {
lock.wait();
}
if(thread.e != null) {
throw thread.e;
}
return thread.result;
}
}

};

return future;
}

}

总结

优点:通过多线程执行并立即返回一个Future对象,而不等待任务,使得源线程继续执行,
只有当源线程需要多线程执行结果,调用其get方法时,通过创建执行线程时创建的对象锁来阻塞线程直到任务执行完成
当执行过程中如果有抛出异常,则先捕获该异常,在调用get执行结果时再抛出

请移步

个人主页: yangyitao.top

JUC相关

欢迎查看Eetal的第八篇博客–JUC相关

JUC

JUC是java.util.concurrent的简写,该包下包含一系列java关于多线程协作相关的类

notify和wait

notify和wait为Object的方法,需要当前线程持有该对象锁,没有调用则会排除非法监管状态的异常,wait使得当前线程放弃该对象锁,进入条件等待队列,notify从该对象锁的条件等待队列中唤醒一个线程,使其进入对象锁的竞争队列

可重入锁和不可重入锁区别

可重入锁使得一个线程内执行的同锁方法之间的调用不需要重新获取锁,比如对象锁—某个对象中的实例方法的互相调用

Lock相关

lock()方法请求锁,如果获取失败则阻塞直到获取成功
unLock()方法释放锁,需要拥有锁才可调用,否则抛出异常
tryLock()方法,尝试获取锁,不阻塞,立即返回,获取成功返回true,获取失败返回false

Lock—Condition

通过lock.newCondition()方法获得,代表一个条件
类似于Object的notify和wait
condition.await() == object.wait()
condition.signal() == object.notify()

Lock与synchonized

synchonized不支持响应中断
Lock可以响应中断,但需要编码完成细节

请移步

个人主页: yangyitao.top