锁优化的建议

来源:互联网 发布:冯大辉 范凯 知乎 编辑:程序博客网 时间:2024/06/05 10:32

1、减小锁持有时间

它的核心思想就是不要在不必需要的代码上加锁,因为线程持有的锁时间过长,相对地,锁的竞争程度也就越激烈。

看下面的代码:

public synchronized void syncMethod() {        othercode1();        mutextMethod();        othercode2();    }    public void syncMethod() {        othercode1();        synchronized (this) {            mutextMethod();        }        othercode2();    }

2、减小锁锁粒度

减小锁粒度也是一种削弱多线程锁竞争的有效手段。典型的使用场景就是ConcurrentHashMap。

对于HashMap来说,最重要的两个方法就是get和put方法。一种最自然的想法就是对整个HashMap加锁,必然可以得到一个线程安全的对象。但是这样做,加锁的粒度太大,对于ConcurrentHashMap,它内部进一步细分了若干个小的HashMap,称之为段 segment。默认情况下ConcurrentHashMap,被进一步细分为16个段。

如果需要在ConcurentHashMap中增加一个新的表项,并不是将整个HashMap加锁,而是首先根据hashcode得到该表项应该被存放到那个段中,然后对该段加锁,并完成put操作。在多线程环境中,如果多个线程同时进行put操作,只要被加入的表项不存放在同一个段中,则线程间便可以做到真正的并行。

@SuppressWarnings("unchecked")    public V put(K key, V value) {        Segment<K,V> s;        if (value == null)            throw new NullPointerException();        int hash = hash(key);        int j = (hash >>> segmentShift) & segmentMask;        if ((s = (Segment<K,V>)UNSAFE.getObject          // nonvolatile; recheck             (segments, (j << SSHIFT) + SBASE)) == null) //  in ensureSegment            s = ensureSegment(j);        return s.put(key, hash, value, false);    }

3、 读写分离锁来替换独占锁

使用读写锁ReadWriteLock可以提供系统的性能。

4、 锁分离

如果将读写锁的思想做进一步的延伸,就是锁分离。典型的案例就是LinkedBlockingQueue的实现。

如果使用独占锁,则要求在两个操作进行时,获取当前队列的独占锁,那么take和put操作就不可能真正的并发,在运行时,它们会彼此等待对方释放锁资源。在这种情况下,锁竞争会相对比较激烈,从而影响程序在高并发的性能。

因此在JDK的实现中,并没有采用这样的方式,取而代之的是把两把不同的锁,分离了take和put操作。

  /** Lock held by take, poll, etc */    private final ReentrantLock takeLock = new ReentrantLock();    /** Wait queue for waiting takes */    private final Condition notEmpty = takeLock.newCondition();    /** Lock held by put, offer, etc */    private final ReentrantLock putLock = new ReentrantLock();    /** Wait queue for waiting puts */    private final Condition notFull = putLock.newCondition();/** Current number of elements */    private final AtomicInteger count = new AtomicInteger(0);

下面来看下take的源码:

 public E take() throws InterruptedException {        E x;        int c = -1;        final AtomicInteger count = this.count;        final ReentrantLock takeLock = this.takeLock;        takeLock.lockInterruptibly();//只能有一个线程take元素。        try {            while (count.get() == 0) {// 没有数据时,线程进入等待状态。                notEmpty.await();            }            x = dequeue(); // 获取一个元素              c = count.getAndDecrement();//元素的数量减一            if (c > 1)                notEmpty.signal();// 大于1,唤醒其他线程进行take操作。        } finally {            takeLock.unlock();        }        if (c == capacity)            signalNotFull();        return x;    }

下面看 put的源码

public void put(E e) throws InterruptedException {        if (e == null) throw new NullPointerException();        // Note: convention in all put/take/etc is to preset local var        // holding count negative to indicate failure unless set.        int c = -1;        Node<E> node = new Node(e);        final ReentrantLock putLock = this.putLock;        final AtomicInteger count = this.count;        putLock.lockInterruptibly();        try {            /*             * Note that count is used in wait guard even though it is             * not protected by lock. This works because count can             * only decrease at this point (all other puts are shut             * out by lock), and we (or some other waiting put) are             * signalled if it ever changes from capacity. Similarly             * for all other uses of count in other wait guards.             */            while (count.get() == capacity) {// 队列已经满了                notFull.await();            }            enqueue(node); // 存放一个元素            c = count.getAndIncrement();//数量加一            if (c + 1 < capacity)// 还有空间,唤醒其他的线程进行put操作。                notFull.signal();        } finally {            putLock.unlock();        }        if (c == 0)            signalNotEmpty();    }

5 、锁粗化

虚拟机在遇到一连串连续地对同一锁不断进行请求和释放的操作时,便会把所有的锁操作整合成对锁的一次请求,从而减少对锁的请求同步次数,这个操作叫做锁的粗化。

看下面的代码就清楚了

public void syncMethod() {        synchronized (this) {            mutextMethod2();        }        synchronized (this) {            mutextMethod2();        }    }
public void syncMethod() {        synchronized (this) {            mutextMethod1();            mutextMethod2();        }    }
原创粉丝点击