廖雪峰Java学习笔记 — 悲观锁与乐观锁、线程安全集合

1. ReadWriteLock

前面说到,ReentrantLock可以替代synchronized实现线程同步,方便我们进行多线程开发。

但是在有些场景下ReentrantLock效率比较低,比如论坛上,大多数人都只是阅读(读),发论坛(写)几率比较小。如果使用ReentrantLock,那么一个用户在读的时候,对象就被锁住了,暂时不再允许其他用户读,这样在读的请求量非常高的情形下,效率比较低。

ReadWriteLock允许多个线程在没有写入时同时读取(提高性能),但只允许一个线程写入。

示例代码如下:

public class Counter {

    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock rLock = rwLock.readLock();
    private final Lock wLock = rwLock.writeLock();
    private final int[] counts = new int[10];

    public void inc(int index) throws InterruptedException{
        wLock.lock();
        try {
            counts[index] += 1;
            Thread.sleep(1000);
        } finally {
            wLock.unlock();
        }
    }

    public int[] get() throws InterruptedException {
        rLock.lock();
        try {
            Thread.sleep(1000);
            return Arrays.copyOf(counts, counts.length);
        } finally {
            rLock.unlock();
        }
    }
}

ReadWriteLock在读的过程中不允许进行写入,需要等待读线程释放锁后才能获取写锁,这种读锁是一种悲观锁



2. StampedLock

StampedLock支持在读的过程中进行写入,能够提高并发效率,但是这样可能会导致读写不一致的问题,所以需要判断在读的过程中是否有写入,如果有写入就再读一次(此时采用悲观锁,不允许其他写操作)就好了,这种读锁是一种乐观锁

示例代码如下:

public class Point {

    private final StampedLock stampedLock = new StampedLock();
    private double x;
    private double y;

    public void move(double deltaX, double deltaY) {
        long stamp = stampedLock.writeLock();
        ArrayBlockingQueue
        try {
            x += deltaX;
            y += deltaY;
        } finally {
            stampedLock.unlockWrite(stamp);
        }
    }

    public double distanceFromOrigin() {
        // 尝试用乐观锁进行读
        long stamp = stampedLock.tryOptimisticRead();
        // 假定原先为(100, 200),那么这里读取x = 100
        double currentX = x;
        /* 这里其他线程可能进行了写操作,写入(300, 400) */
        // 那么这里读取y = 400
        double currentY = y;
        // 此时坐标为(100, 400),是不对的。
        // 判断是否在读的过程中进行了写入
        if (!stampedLock.validate(stamp)) {
            // 使用悲观锁再读一次
            stamp = stampedLock.readLock();
            try {
                currentX = x;
                currentY = y;
            } finally {
                stampedLock.unlockRead(stamp);
            }
        }
        return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

因为写入的几率比较小,所以大部分情况下代码中的if是进不去的,性能不会受到很多影响,同时注意这里的StampedLock是一种不可重入锁



3. 线程安全的集合

在多线程编程中,集合也是经常会使用过的。

但我们常见的集合,例如ArrayListMapQueue等都是非线程安全的,需要转换成对应的线程安全集合。

Java标准库的java.util.concurrent包提供的线程安全的集合,对应关系如下表:

interfacenon-thread-safethread-safe
ListArrayListCopyOnWriteArrayList
MapHashMapConcurrentHashMap
SetHashSet / TreeSetCopyOnWriteArraySet
QueueArrayDeque / LinkedListArrayBlockingQueue / LinkedBlockingQueue
DequeArrayDeque / LinkedListLinkedBlockingDeque

由于它们接口一样,所以在使用时只需要修改引用对象就行了,例如把

Queue<String> queue = new LinkedList<>();

修改成

Queue<String> queue = new LinkedBlockingQueue<>();

就可以了。



4. Atomic

atomic提供了一些原子操作,有时一些简单的操作,例如全局唯一ID生成器借助atomic就可以以无锁的方式实现线程安全,可以简化一些代码操作。



5. 参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值