锁优化-减小锁粒度

锁优化有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并发编程实战》

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
优化是指在多线程编程中,通过改进的机制和使用方式来提高程序的性能和并发能力。synchronized关键字是Java中最常用的机制之一,它可以保证同一时间只有一个线程可以进入被synchronized修饰的代码块。下面是一些synchronized优化的方法: 1. 减小粒度:如果在一个方法中有多个synchronized代码块,可以考虑将这些代码块拆分成多个方法,以减小粒度。这样可以使得多个线程可以并发执行不同的代码块,提高程序的并发性能。 2. 使用局部变量替代成员变量:在使用synchronized关键字时,尽量使用局部变量而不是成员变量。因为成员变量的访问需要通过对象实例来进行,而局部变量的访问是线程私有的,不需要加。 3. 使用同步代码块代替同步方法:在某些情况下,使用同步代码块比使用同步方法更加灵活。同步代码块可以指定粒度,只对需要同步的代码进行加,而不是整个方法。 4. 使用volatile关键字:volatile关键字可以保证变量的可见性和禁止指令重排序,可以在一定程度上替代synchronized关键字。但是需要注意,volatile关键字只能保证单个变量的原子性,不能保证多个操作的原子性。 5. 使用Lock接口:Java提供了Lock接口及其实现类ReentrantLock,相比于synchronized关键字,Lock接口提供了更加灵活的机制。可以手动控制的获取和释放,可以实现公平和非公平,并且支持多个条件变量。 6. 使用读写:如果在多线程环境下,读操作远远多于写操作,可以考虑使用读写ReadWriteLock来提高程序的并发性能。读写允许多个线程同时读取共享数据,但只允许一个线程写入共享数据。 7. 使用并发集合类:Java提供了一些并发集合类,ConcurrentHashMap、ConcurrentLinkedQueue等,它们内部使用了一些优化的技术,可以提高多线程环境下的并发性能。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值