分布式协调工具的锁机制

分析性能问题

在之前的分布式协调工具中使用的是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锁释放

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值