java并发编程性能与可伸缩性

Posted 寰殇丶天使

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java并发编程性能与可伸缩性相关的知识,希望对你有一定的参考价值。

性能与可伸缩性

一、Amdahl定律

1.问题和资源的关系

    在某些问题中,资源越多解决速度越快;而有些问题则相反:

  注意:每个程序中必然有串行的部分,而合理的分析出串行和并行的部分对程序的影响极大;串行部分占比和多核执行效率之间是指数级别的关系

2.ConcurrentLinkedQueue

  在多核环境中,这个线程安全的队列比通过synchronizedList生成的队列速度要快很多

  可以说:concurrent中提供的类,比通过方法生成的线程安全类速度要快

二、线程开销

  由于多线程有开销:所以使用多线程必须保证性能的提升>并发的开销

  上下文切换的开销

  内存同步的开销

三、减少锁的竞争

  1.减少锁持有时间:缩小锁的范围

private final Map<String, String> attributes = new HashMap<String, String>();

//整个方法上锁
    public synchronized boolean userLocationMatches(String name, String regexp) {
        String key = "users." + name + ".location";
        String location = attributes.get(key);
        if (location == null)
            return false;
        else
            return Pattern.matches(regexp, location);
    }

public boolean userLocationMatches(String name, String regexp) {
        String key = "users." + name + ".location";
        String location;
        //只针对可变状态上锁
        synchronized (this) {
            location = attributes.get(key);
        }
        if (location == null)
            return false;
        else
            return Pattern.matches(regexp, location);
    }

 

  2.降低锁的请求频率:锁分解、锁分段...

    锁分解:将一个锁分解为多个锁如:无需在一个原子操作中更新多个状态变量,每个状态变量却用的是同一个类锁,就没必要,每个不相干的状态变量的使用自己的锁就行

public class ServerStatusBeforeSplit {
    public final Set<String> users;
    public final Set<String> queries;

    public ServerStatusBeforeSplit() {
        users = new HashSet<String>();
        queries = new HashSet<String>();
    }
    //每个方法使用 当前class实例锁,类似于synchronized(this),不管是否是操作同一共享状态
    public synchronized void addUser(String u) {
        users.add(u);
    }

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

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

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

public class ServerStatusAfterSplit {
    public final Set<String> users;
    public final Set<String> queries;
    //操作同一 状态的方法 使用相同的锁
    public ServerStatusAfterSplit() {
        users = new HashSet<String>();
        queries = new HashSet<String>();
    }
    public void addUser(String u) {
        synchronized (users) {
            users.add(u);
        }
    }
    public void addQuery(String q) {
        synchronized (queries) {
            queries.add(q);
        }
    }
    public void removeUser(String u) {
        synchronized (users) {
            users.remove(u);
        }
    }
    public void removeQuery(String q) {
        synchronized (users) {
            queries.remove(q);
        }
    }
}

 

  

  锁分段:如将map桶分成不同的段,每个段都有一个锁,这样,在执行某些操作如get,就可以持有不同的锁从而提高并发效率,当然有些操作需要同时持有容器所有段的锁如clear等

//Map分段锁实现
public class StripedMap {
    // Synchronization policy: buckets[n] guarded by locks[n%N_LOCKS]
    private static final int N_LOCKS = 16;  //锁数量
    private final Node[] buckets;           //容器桶
    private final Object[] locks;           //同步监听器对象数组
    private static class Node {
        Node next;
        Object key;
        Object value;
    }

    public StripedMap(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);
        //获取当前 key对应的index区域的锁,只获取了一个锁
        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 clear() {
        for (int i = 0; i < buckets.length; i++) {
            //获取 每个i对应的锁,就是获取了整个容器所有的分段锁
            synchronized (locks[i % N_LOCKS]) {
                buckets[i] = null;
            }
        }
    }
}

 

 

  3.避免热点域

    热点资源的锁竞争激烈,导致的性能问题

  4.替代独占锁

    如:读-写锁:读读可并行,来防止独占;使用原子状态量;使用并发容器;使用不可变对象等

  5.减少上下文切换

    任务在阻塞于非阻塞的状态中切换,就类似于一次上下文切换

    如:日志,日志的打印和IO操作会导致大量的阻塞和释放,导致性能问题

 

以上是关于java并发编程性能与可伸缩性的主要内容,如果未能解决你的问题,请参考以下文章

《java并发编程实战》读书笔记8--死锁,性能与可伸缩性,锁粒度锁分解锁分段

深入了解Java并发——《Java Concurrency in Practice》11.性能与可伸缩性

Java进阶知识点:服务端高并发的基石 - NIO与Reactor AIO与Proactor

Java进阶知识点5:服务端高并发的基石 - NIO与Reactor模式以及AIO与Proactor模式

第十二章:并发程序的测试——Java并发编程实战

java并发编程实战:第十二章---并发程序的测试