分析性能问题
在之前的分布式协调工具中使用的是Arrayist来作为等待锁的等待者的队列,但是这样在释放锁和加锁时就需要注意线程安全问题,需要为加锁和释放锁使用同一个锁。这样无疑是降低了加锁和释放锁的速度。同时arrayList的删除和增加也是一个耗时的操作,添加的耗时主要体现在超出容量限制时的容量扩增那一块。
解决方案
将使用自定义的link来替代arrayList,并且定义添加只在头部添加。删除有两个地方,一个是尾部,还有一个是当前线程所在的link节点,这个节点可能是头部。所以在这里需要注意一下。
在并发量很大的时候可以看做插入式一把锁,删除是另一把锁。可以减少锁的冲突。使用link添加的时候很快,删除尾部也很快,那么在删除中间的时候可能需要遍历,并且查看某个线程所在的节点是否已经在link中也需要遍历节点,但是这样就有需要遍历整个link列表,平均的时间复杂度为O(N),这个不是我所期望的,我期望的是O(1),所以此时就需要空间换时间了,使用map来记录已存在的link,thread为key,link节点为value。
当判断某一节点是否在link中,只需要判断当前的线程是否在map中即可,删除时,也只需要通过thread提取出link节点然后删除即可,总的来说,插入和删除的时间复杂度均为O(1)。大大提高了插入和删除的速度。
具体的实施细节
字段定义:
// 等待列表
private volatile waitLink head = null,tail = null;
// 用于快速检查当前线程是否已经存在于列表中,其实在这里加锁和释放锁还是有所交锋的(虽然概率很小),只是锁的粒度很小而已。
private ConcurrentHashMap<Thread,waitLink> isInLink = new ConcurrentHashMap<>();
// 尾锁
private final Object linkLastLock = new Object();
节点信息存储和链表定义:
class waitNode extends WeakReference<Thread> {
public waitNode(Thread referent) {
super(referent);
}
}
/**
* 删除或是在尾部或是在中间位置,极少会使头部,当然如果并发量没有那么高的话其实是会在头部的。
* 断开 断开 断开
* (可能)(lockOwner) (唤醒)
* ---- ---- ----
* |node|<-->|node|<-->|node|
* ---- ---- ----
* 添加总是在头部添加
* 添加
* 等待者
* ---- ---- ----
* |node|<-->|node|<-->|node|
* ---- ---- ----
*/
class waitLink{
waitLink pre = null,next = null;
waitNode node = null;
public waitLink(){
}
public waitLink(waitNode node){
this.node = node;
}
}
判断当前线程所代表的的节点是否在link中:
public boolean isCurrentThreadInLink(Thread thread){
return isInLink.containsKey(thread);
}
加锁操作其实没有什么变化,其实就是将之前的arrayList的部分换成了link:
public boolean tryLock(NodeInterface lockNode) throws RemoteException{
/**
* 每一次的远程调用使用的都是不同的对象,所以锁也是不一样的,这个时候应该将锁提取出来,不应该放在这里。
* 使用不同的线程锁都不一样。
*/
System.out.println(Thread.currentThread().getName()+"-----"+lockNode.getThisNodeType());
Thread thread = Thread.currentThread();
if (lockOwner == null) {
synchronized (distributeLock) {
if (lockOwner == null) {
System.out.println(Thread.currentThread().getName()+"-----"+lockNode.getThisNodeType());
lockOwner = lockNode;
waitLink waitLink = new waitLink(new waitNode(thread));
setHead(waitLink);
isInLink.put(thread,waitLink);
// waiterList.add(thread);
reLockTimes.incrementAndGet();
System.out.println(Thread.currentThread().getName()+"-----"+lockNode.getThisNodeType() + "获取锁");
return true;
} else if (lockOwner == lockNode) { //在这里,如程序走到这里,其他线程恰好将lockOwner置空,那么将会报错
reLockTimes.incrementAndGet();
return true;
}else {
if (!isCurrentThreadInLink(thread)){
waitLink waitLink = new waitLink(new waitNode(thread));
addNode(waitLink);
isInLink.put(thread,waitLink);
System.out.println(Thread.currentThread().getName() + "-----" + lockNode.getThisNodeType() + "加入队列等待");
}
// if (!waiterList.contains(thread)) {
// waiterList.add(thread);
// System.out.println(Thread.currentThread().getName() + "-----" + lockNode.getThisNodeType() + "加入队列等待");
// }
}
}
}else if (lockOwner == lockNode){
reLockTimes.incrementAndGet();
return true;
}else {
synchronized (distributeLock) {
if (!isCurrentThreadInLink(thread)){
waitLink waitLink = new waitLink(new waitNode(thread));
addNode(waitLink);
isInLink.put(thread,waitLink);
System.out.println(Thread.currentThread().getName() + "-----" + lockNode.getThisNodeType() + "加入队列等待");
}
// if (!waiterList.contains(thread)) {
// waiterList.add(thread);
// System.out.println(Thread.currentThread().getName() + "-----" + lockNode.getThisNodeType() + "加入队列等待");
// }
}
}
/**
* 枷锁失败则将其加入到等待列表中,等待主节点释放锁
*/
try {
/**
* 等待锁释放并重新抢锁
* 非公平抢锁
*/
LockSupport.park();
System.out.println(Thread.currentThread().getName() + "-----" + lockNode.getThisNodeType() + "被唤醒");
return tryLock(lockNode);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(thread.getName()+"失败了");
return false;
}
重要的是释放锁的过程:
public boolean tryRelease(NodeInterface releaseNode) throws RemoteException {
// System.out.println(Thread.currentThread().getName());
if (isLockOwner(releaseNode)) {
Thread thread = Thread.currentThread();
synchronized (distributeLock) { //1
reLockTimes.decrementAndGet();
if (reLockTimes.get() == 0) {
//安全的置空
lockOwner = null;
System.out.println(Thread.currentThread().getName() + "-----" + releaseNode.getThisNodeType() + "锁释放");
}
}
try {
/**
* 唤醒后面的线程
* 使用holdLockNode进行加速删除
* 这里使用的原因是在删除holdLockNode的时候会用到前一个节点,
* 所以要保证前一个节点也不是head的情况下可以实现安全操作
*/
synchronized (linkLastLock){//2
waitLink waitLink1 = isInLink.get(thread);
if (waitLink1 == head) {//3
synchronized (distributeLock) {
deleteNode(waitLink1);
isInLink.remove(thread);
// waiterList.remove(Thread.currentThread());
if (head != null) {
waitLink waitLink = deleteTailNode();
LockSupport.unpark(waitLink.node.get());
Thread thread1 = waitLink.node.get();
/**
* 如果这里不删除,只是简单的执行waitLink = null;那么代码会出现问题,
* 所以还需要将其从map中删除
*/
if (thread1!=null)
isInLink.remove(thread1);
waitLink.node = null;
waitLink = null;
}
}
}else{//4
/**
* 由于holdLockNode会在trylock中被重新赋值,之前的那个就没有人格引用指向了,
* 会被垃圾回收期发现并回收掉所以没有内存泄漏的问题
*/
deleteNode(waitLink1);
isInLink.remove(thread);
// waiterList.remove(Thread.currentThread());
if (head != null) {
waitLink waitLink = deleteTailNode();
LockSupport.unpark(waitLink.node.get());
Thread thread1 = waitLink.node.get();
if (thread1!=null)
isInLink.remove(thread1);
waitLink.node = null;
waitLink = null;
}
}
}
// if (waiterList.size() > 0)
// LockSupport.unpark(waiterList.remove(0));
// System.out.println("当前剩余的等待者数量为:" + waiterList.size());
// Thread.sleep(10);
} catch(Exception e){
e.printStackTrace();
}
return true;
}else return false;
}
//1的代码表示拥有当前锁的线程已经进入了核心阶段,并且锁的重入已经结束。然后解锁这里的锁和加锁里的锁是一样的,因为他们都操控了同一个东西lockOwner。//2表示以及将lockOwner释放,现在是尾部锁加持阶段。//3由于有可能当前的节点是head就需要加持加锁的锁,为什么呢?如果是head的其实很容易想到,如果是head的下一个节点那么如果当前有节点插入的话,本节点要删除那么插入操作会操控head的pre,产出操作会操控head的next,他们操控的是同一个对象的不同属性,其实是没有冲突的所以不需要加上//4就是正常的节点删除。
其实在者之间还出现过一个错误就是null错误,也是查了半天才知道有一行代码没写:
if (thread1!=null)
isInLink.remove(thread1);
由于执行了waitLink = null;所以在加锁判断的时候哦安短thread已经在其中了就不会创建新的,然后再解锁中提取出来的是一个null,所以报错。
测试
测试客户端代码,这里只开启了100个线程:
public class start {
public static AtomicInteger integer = new AtomicInteger();
/**
*
* @param args 参数,0:domin端口
*/
public static void main(String[] args) {
String remoteHost = null;
if (args.length>0) {
nodeConfig.port = Integer.parseInt(args[0]);
nodeAllConfig.localhost = args[1];
nodeAllConfig.thisuid = nodeAllConfig.localhost + ":" + nodeAllConfig.port;
nodeAllConfig.port = Integer.parseInt(args[2]);
remoteHost = args[3];
}else remoteHost = "localhost";
Export export = new Export();
Achieve achieve = new Achieve();
try {
export.registryNewPort(nodeAllConfig.port);
DominInterface domin = null;
domin = (DominInterface)achieve.getRemoteObjectForDomin("domin", remoteHost, nodeConfig.port);
TreeMap<String, NodeInterface> allNodes = domin.getAllNodes();
NodeInterface create = allNodes.get("create");
DateNode dateNode = new DateNode();
dateNode.setValues("hello".getBytes());
SayHello sayHello = new SayHello(create.createNode(nodeAllConfig.thisuid, Thread.currentThread().getName(),dateNode,"test"));
export.exportObjectsForNewRegistry(sayHello,sayHello.getThisNodeType());
domin.addNode(sayHello, nodeAllConfig.localhost,nodeAllConfig.port);
domin.setWatch(sayHello.getThisNodeType(),sayHello.getThisCurrentNode(),"test");
sayHello.getThisNodevalve().clear();
dateNode.setValues("再来一次".getBytes());
sayHello.getThisNodevalve().add(dateNode);
Class<? extends SayHello> aClass = sayHello.getClass();
Class<?> superclass = aClass.getSuperclass();
Field upDateTime = superclass.getDeclaredField("updateTime");
upDateTime.setAccessible(true);
upDateTime.set(sayHello,System.currentTimeMillis());
domin.modifyNode(sayHello,nodeAllConfig.localhost,nodeAllConfig.port);
System.out.println(domin.getGlobalIndex());
domin.setGlobalLockForNode(Thread.currentThread().getName());
DistributeLock remoteObjectForDistributeLock = null;
String password = Thread.currentThread().getName();
Thread[] threads = new Thread[100];
for (int i = 0; i < threads.length; i++) {
threads[i] = new myThread(password,achieve);
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
Thread.sleep(10);
}
} catch (RemoteException | NoSuchFieldException | IllegalAccessException | InterruptedException e) {
e.printStackTrace();
}
}
static class myThread extends Thread{
private String password = null;
private Achieve achieve = null;
public myThread(String password,Achieve achieve) {
this.password = password;
this.achieve = achieve;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
try {
NodeChild nodeChild = null;
nodeChild = new NodeChild(nodeAllConfig.thisuid, Thread.currentThread().getName(), nodeAllConfig.thisuid,
System.currentTimeMillis(), password, new DateNode("lock".getBytes()), -1);
DistributeLock remoteObjectForDistributeLock = achieve.getRemoteObjectForDistributeLock(nodeConfig.distributeLock + "_" + password,
"localhost", nodeConfig.port);
boolean b = remoteObjectForDistributeLock.tryLock(nodeChild);
if (b) {
System.out.println("/上锁" + Thread.currentThread().getName());
boolean b1 = remoteObjectForDistributeLock.tryRelease(nodeChild);
if (b1) { //1
System.out.println("/开锁" + Thread.currentThread().getName());
int i = integer.incrementAndGet();
System.out.println("成功--》"+i);
}else
System.out.println(Thread.currentThread().getName()+"解锁失败"+b1);
}else{
System.out.println(Thread.currentThread().getName()+"加锁失败"+b);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
//1只有加锁和解锁都成功的才会是的计数器加1。
结果展示
客户端
监听的数据发生了改变。。。
1
Thread-1
Thread-2
Thread-3
/上锁Thread-1
/开锁Thread-1
成功–》1
Thread-4
/上锁Thread-3
/开锁Thread-3
成功–》2
/上锁Thread-4
/开锁Thread-4
成功–》3
Thread-5
/上锁Thread-2
/开锁Thread-2
成功–》4
/上锁Thread-5
Thread-6
/开锁Thread-5
成功–》5
。。。。。。。。
Thread-96
/上锁Thread-96
/开锁Thread-96
成功–》96
Thread-97
/上锁Thread-97
/开锁Thread-97
成功–》97
Thread-98
/上锁Thread-98
/开锁Thread-98
成功–》98
Thread-99
/上锁Thread-99
/开锁Thread-99
成功–》99
Thread-100
/上锁Thread-100
/开锁Thread-100
成功–》100
计数器为100表示所有线程都已经完成了加锁和解锁的功能
主节点
RMI TCP Connection(8)-192.168.127.1-----Thread-98获取锁
RMI TCP Connection(8)-192.168.127.1-----Thread-98锁释放
RMI TCP Connection(8)-192.168.127.1-----Thread-99
RMI TCP Connection(8)-192.168.127.1-----Thread-99
RMI TCP Connection(8)-192.168.127.1-----Thread-99获取锁
RMI TCP Connection(8)-192.168.127.1-----Thread-99锁释放
RMI TCP Connection(8)-192.168.127.1-----Thread-100
RMI TCP Connection(8)-192.168.127.1-----Thread-100
RMI TCP Connection(8)-192.168.127.1-----Thread-100获取锁
RMI TCP Connection(8)-192.168.127.1-----Thread-100锁释放
在客户端中可以发现每一个线程开启是睡眠了10毫秒的,现在我们将其去掉在测试一下:
客户端
监听的数据发生了改变。。。
1
Thread-1
Thread-3
Thread-2
Thread-4
Thread-7
Thread-5
Thread-6
Thread-8
。。。。
Thread-98
Thread-95
Thread-99
Thread-96
Thread-100
/上锁Thread-1
/开锁Thread-1
成功–》1
。。。。
/上锁Thread-72
/开锁Thread-72
成功–》97
/上锁Thread-37
/开锁Thread-37
成功–》98
/上锁Thread-63
/开锁Thread-63
成功–》99
/上锁Thread-9
/开锁Thread-9
成功–》100
计数器为100表示所有线程都已经完成了加锁和解锁的功能
主节点
RMI TCP Connection(7)-192.168.127.1-----Thread-1
RMI TCP Connection(7)-192.168.127.1-----Thread-1
RMI TCP Connection(8)-192.168.127.1-----Thread-7
RMI TCP Connection(10)-192.168.127.1-----Thread-9
RMI TCP Connection(7)-192.168.127.1-----Thread-1获取锁
RMI TCP Connection(38)-192.168.127.1-----Thread-21
。。。。
RMI TCP Connection(51)-192.168.127.1-----Thread-49
RMI TCP Connection(10)-192.168.127.1-----Thread-9加入队列等待
RMI TCP Connection(61)-192.168.127.1-----Thread-63
RMI TCP Connection(61)-192.168.127.1-----Thread-63加入队列等待
RMI TCP Connection(15)-192.168.127.1-----Thread-15
RMI TCP Connection(46)-192.168.127.1-----Thread-29
RMI TCP Connection(37)-192.168.127.1-----Thread-44
RMI TCP Connection(15)-192.168.127.1-----Thread-15加入队列等待
RMI TCP Connection(43)-192.168.127.1-----Thread-53
RMI TCP Connection(43)-192.168.127.1-----Thread-53加入队列等待
RMI TCP Connection(28)-192.168.127.1-----Thread-50
。。。。
RMI TCP Connection(56)-192.168.127.1-----Thread-26
RMI TCP Connection(8)-192.168.127.1-----Thread-7加入队列等待
RMI TCP Connection(64)-192.168.127.1-----Thread-83
。。。。
RMI TCP Connection(63)-192.168.127.1-----Thread-41
RMI TCP Connection(56)-192.168.127.1-----Thread-26加入队列等待
RMI TCP Connection(57)-192.168.127.1-----Thread-70
。。。。
RMI TCP Connection(84)-192.168.127.1-----Thread-99
RMI TCP Connection(42)-192.168.127.1-----Thread-22加入队列等待
RMI TCP Connection(76)-192.168.127.1-----Thread-74
。。。。
RMI TCP Connection(78)-192.168.127.1-----Thread-62
RMI TCP Connection(76)-192.168.127.1-----Thread-74加入队列等待
RMI TCP Connection(89)-192.168.127.1-----Thread-93
RMI TCP Connection(79)-192.168.127.1-----Thread-20
RMI TCP Connection(58)-192.168.127.1-----Thread-60加入队列等待
RMI TCP Connection(83)-192.168.127.1-----Thread-98
RMI TCP Connection(93)-192.168.127.1-----Thread-86
RMI TCP Connection(44)-192.168.127.1-----Thread-18加入队列等待
RMI TCP Connection(90)-192.168.127.1-----Thread-75
RMI TCP Connection(28)-192.168.127.1-----Thread-50加入队列等待
RMI TCP Connection(37)-192.168.127.1-----Thread-44加入队列等待
。。。。
RMI TCP Connection(9)-192.168.127.1-----Thread-8加入队列等待
RMI TCP Connection(38)-192.168.127.1-----Thread-21加入队列等待
RMI TCP Connection(90)-192.168.127.1-----Thread-75加入队列等待
。。。。
RMI TCP Connection(85)-192.168.127.1-----Thread-66加入队列等待
RMI TCP Connection(87)-192.168.127.1-----Thread-100加入队列等待
RMI TCP Connection(7)-192.168.127.1-----Thread-1锁释放
RMI TCP Connection(71)-192.168.127.1-----Thread-81加入队列等待
RMI TCP Connection(68)-192.168.127.1-----Thread-82加入队列等待
RMI TCP Connection(86)-192.168.127.1-----Thread-69加入队列等待
。。。。
RMI TCP Connection(55)-192.168.127.1-----Thread-84加入队列等待
RMI TCP Connection(64)-192.168.127.1-----Thread-83加入队列等待
RMI TCP Connection(64)-192.168.127.1-----Thread-83被唤醒
RMI TCP Connection(64)-192.168.127.1-----Thread-83
RMI TCP Connection(64)-192.168.127.1-----Thread-83
RMI TCP Connection(64)-192.168.127.1-----Thread-83获取锁
RMI TCP Connection(64)-192.168.127.1-----Thread-83锁释放
RMI TCP Connection(55)-192.168.127.1-----Thread-84被唤醒
RMI TCP Connection(61)-192.168.127.1-----Thread-63获取锁
RMI TCP Connection(61)-192.168.127.1-----Thread-63锁释放
RMI TCP Connection(10)-192.168.127.1-----Thread-9被唤醒
RMI TCP Connection(10)-192.168.127.1-----Thread-9
RMI TCP Connection(10)-192.168.127.1-----Thread-9
RMI TCP Connection(10)-192.168.127.1-----Thread-9获取锁
RMI TCP Connection(10)-192.168.127.1-----Thread-9锁释放