锁优化-减小锁粒度

锁优化有3个方向:

1.减少持有锁的时间:例如,将CPU密集和I/O密集的任务移到锁外,可以有效减少持有锁的时间,从而降低其他线程的阻塞时间。

2.减小加锁的粒度:将单个独占锁变为多个锁,从而将加锁请求均分到多个锁上,有效降低对锁的竞争。但是,增加锁的前提是多线程访问的变量间相互独立,如果多线程需要同时访问多个变量,则很难进行锁分解,因为要维持原子性。

3.放弃使用独占锁,使用非阻塞算法来保证并发安全。例如,使用并发容器、读写锁、不可变对象、原子变量、线程封闭等技术。

本文介绍减小锁粒度的方法:

public class ServerStatus {

    public final Set<String> users;
    public final Set<String> queries;

    public ServerStatus() {
        users = new HashSet<>();
        queries = new HashSet<>();
    }

    /**
     * 独占锁意味着增加资源不能提升程序吞吐量,可伸缩性很差
     * @param user
     */
    public synchronized void addUser(String user) {
        users.add(user);
    }

    public synchronized  void addQuery(String q) {
        queries.add(q);
    }

    public synchronized void removeUser(String user) {
        users.remove(user);
    }

    public synchronized  void removeQuery(String query) {
        users.remove(query);
    }
}
/**
 * 锁分解技术是减小了锁粒度,将锁住所有变量的全局锁变为锁住部分变量的局部锁。
 * 锁分解能提升吞吐量的原理是将对一个锁的竞争均摊到2个锁上,从而降低了对单个锁的请求频率,有效地降低了竞争程度。
 * 不足:与独占锁相比,锁分解技术在多处理器系统中只能将吞吐量提高一倍,可伸缩性还是受到限制。
 */
public class ServerStatusLockDecompose {

    public final Set<String> users;
    public final Set<String> queries;

    public ServerStatusLockDecompose() {
        users = new HashSet<>();
        queries = new HashSet<>();
    }

    /**
     * 独占锁意味着增加资源不能提升程序吞吐量,可伸缩性很差
     * @param user
     */
    public  void addUser(String user) {
        synchronized(users) {
            users.add(user);
        }
    }

    public  void addQuery(String q) {
        synchronized(queries) {
            queries.add(q);
        }
    }

    public void removeUser(String user) {
        synchronized(users) {
            users.remove(user);
        }
    }

    public  void removeQuery(String query) {
        synchronized(queries) {
            queries.remove(query);
        }
    }
}

当处理器增加时,分解锁的性能不能持续提升,所以,可以使用分段锁,要求是数据独立同分布,每个锁保护一部分数据。

/**
 * buckets[n]由locks[n % N_LOCKS]来保护
 */
public class StrippedMap {

    private static final int  N_LOCKS = 16;

    private final Node[] buckets;
    private final Object[] locks;

    private static class Node{
        Object key;
        Object value;
        Node next;

        Node(Object key, Object value) {
            this.value = value;
            this.key = key;
        }

        void setNext(Node next) {
            this.next = next;
        }
    }

    public StrippedMap(int numBuckets) {
        buckets = new Node[numBuckets];
        locks = new Object[N_LOCKS];
        for(int i = 0; i < N_LOCKS; i++) {
            locks[i] = new Object();
        }
    }

    private final int hash(Object key) {
        return Math.abs(key.hashCode() % buckets.length);
    }

    public Object get(Object key) {
        int hash = hash(key);
        synchronized (locks[hash % N_LOCKS]) {
            for(Node m = buckets[hash]; m != null; m = m.next) {
                if(m.key.equals(key)) {
                    return m.value;
                }
            }
        }
        return null;
    }

    public void put(Object key, Object value) {
        int hash = hash(key);
        synchronized (locks[hash % N_LOCKS]) {
            Node node = buckets[hash];
            Node preNode = node;
            while (node != null) {
                preNode = node;
                node = node.next;
            }
            Node currentNode = new Node(key,value);
            currentNode.setNext(null);
            if(preNode == null) {
                buckets[hash] = currentNode;
                return;
            }
            preNode.setNext(currentNode);
            return;
        }
    }

    public void clear() {
        for(int i = 0; i < buckets.length; i++) {
            synchronized (locks[i % N_LOCKS]) {
                buckets[i] = null;
            }
        }
    }

    public static void main(String[] args) {
        StrippedMap map = new StrippedMap(16);
        map.put("test", 123);
        map.put("test1", 1);
        map.put("abs", 1);
        System.out.println(map.get("abs"));
        System.out.println(map.get("test"));
        System.out.println(map.get("test1"));
    }
}

对锁的优化,是建立在保证程序正确性的基础上的,所以,先保证程序能正常运行,当吞吐量达不到期望时,才能考虑性能优化,过早的优化就是灾难。

参考《Java并发编程实战》

猜你喜欢

转载自blog.csdn.net/Wengzhengcun/article/details/87782429