并发列表

并发链表中锁的使用及优化

【概念】

如果存在一系列的节点,以head节点为开始节点,以b为结束节点,且这个节点序列中,每个节点都指向其successor节点,则称节点b是可达的。

一个对象在集合中当且仅当这个对象从head节点触发时可达的。

【案例1】 一个并发list的简单实现


    package com.reign.gcmob.concurrency;

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class CoarseList {
        private class Node {
            T item;
            int key;
            Node next;
    
            public Node(int key) {
                this.key = key;
            }
    
            public Node(T item) {
                this.item = item;
            }
        }
    
        private Node head;
        private Lock lock = new ReentrantLock();
    
        public CoarseList() {
            head = new Node(Integer.MIN_VALUE);
            head.next = new Node(Integer.MAX_VALUE);
        }
    
        public boolean add(T item) {
            Node pred, curr;
            int key = item.hashCode();
            lock.lock();
            try {
                pred = head;
                curr = pred.next;
                while (curr.key < key) {
                    pred = curr;
                    curr = curr.next;
                }
                if (key == curr.key) {
                    return false;
                } else {
                    Node node = new Node(item);
                    node.next = curr;
                    pred.next = node;
                    return true;
                }
            } finally {
                lock.unlock();
            }
        }

        public boolean remove(T item) {
            Node pred, curr;
            int key = item.hashCode();
            lock.lock();
            try {
                pred = head;
                curr = pred.next;
                while (curr.key < key) {
                    pred = curr;
                    curr = curr.next;
                }
                if (key == curr.key) {
                    pred.next = curr.next;
                    return true;
                } else {
                    return false;
                }
            } finally {
                lock.unlock();
            }
        }
    
    }


上述实现的优缺点分析:

如果并发量很低时,这个CoarseList是一种非常好的list的实现。
如果并发量很大时,即使锁的性能很好,则总存在线程出现超时等待的问题。

【案例2】 一种对案例1的CoarseListFineList实现


    public class FineList {
    
        private class Node {
            T item;
            int key;
            Node next;
            private Lock lock = new ReentrantLock();
    
            public Node(int key) {
                this.key = key;
            }
    
            public Node(T item) {
                this.item = item;
            }
    
            public void lock() {
                lock.lock();
            }
    
            public void unlock() {
                lock.unlock();
            }
        }
    
        private Node head;
    
        public FineList() {
            head = new Node(Integer.MIN_VALUE);
            head.next = new Node(Integer.MAX_VALUE);
        }
    
        public boolean add(T item) {
            int key = item.hashCode();
            head.lock();
            Node pred = head;
            try {
                Node curr = pred.next;
                curr.lock();
                try {
                    while (curr.key < key) {
                        pred.unlock();
                        pred = curr;
                        curr = curr.next;
                        curr.lock();
                    }
                    if (curr.key == key) {
                        return false;
                    }
                    Node newNode = new Node(item);
                    newNode.next = curr;
                    pred.next = newNode;
                    return true;
                } finally {
                    curr.unlock();
                }
            } finally {
                pred.unlock();
            }
        }
    
        public boolean remove(T item) {
            Node pred = null, curr = null;
            int key = item.hashCode();
            head.lock();
            try {
                pred = head;
                curr = pred.next;
                curr.lock();
                try {
                    while (curr.key < key) {
                        pred.unlock();
                        pred = curr;
                        curr = curr.next;
                        pred.lock();
                        curr.lock();
                    }
                    if (curr.key == key) {
                        pred.next = curr.next;
                        return true;
                    }
                    return false;
                } finally {
                    curr.unlock();
                }
            } finally {
                pred.unlock();
            }
        }
    
    }

分析:

  1. 为什么FineListremove方法需要两把锁?

反证法: 假设只使用一把锁来实现FineListremove方法,比如下面的实现


        public boolean remove(T item) {
            Node pred = null, curr = null;
            int key = item.hashCode();
            head.lock();
            try {
                pred = head;
                curr = pred.next;
                while (curr.key < key) {
                    pred.unlock();
                    pred = curr;
                    curr = curr.next;
                    pred.lock();
                }
                if (curr.key == key) {
                    pred.next = curr.next;
                    return true;
                }
                return false;
            } finally {
                pred.unlock();
            }
        }

此时,假设有两个线程A和B来操作FineList,线程A打算删除key=a的节点,线程B打算删除key=b的节点,其中节点anext指向节点b

假设线程B先调用remove方法,根据上述代码依次获取head节点的锁->释放head节点的锁->获得a节点的锁->释放a节点的锁;线程A后调用remove方法,获取head节点的锁。此时,由于head节点的锁跟a节点的锁是不一样的,则线程B删除节点b的代码和线程A删除节点a的代码是可以并发执行的,就会导致如下现象,最终结果是只删除了节点a

并发列表_第1张图片
并发列表remove--一把锁失败示意图.png

介绍完只用一把锁来实现remove方法存在的问题,下面来分析为什么两把锁是成功的?

假设线程B先调用remove方法,则锁的获取顺序为获取head节点的锁->释放head节点的锁->获得a节点的锁->获得b节点的锁->释放b节点的锁->释放a节点的锁;线程A后调用remove方法,则锁的获取顺序为获取head节点的锁->获得a节点的锁->释放a节点的锁->释放head节点的锁。注意两个线程此时会竞争获取节点a的锁,就不会出现一把锁的引起的问题。

并发列表_第2张图片
TwoLockSuccess.png
  1. 不论是add方法还是remove方法,锁的获取顺序必须是从head节点开始,然后next引用,直到tail节点。如果add方法和remove获取锁的顺序不一样,就有可能造成死锁,比如下图所示:线程A打算添加a节点,获取锁的顺序是已经获得b节点的锁-->尝试着再获取head节点的锁;线程B打算删除节点b,获取锁的顺序是已经获得head节点的锁-->尝试着再获取b节点的锁,即
并发列表_第3张图片
FineList-DeadLock.png

你可能感兴趣的:(并发列表)