Java并发编程-锁

锁的主要知识点有:

  • 悲观锁/乐观锁
  • 独享锁/共享锁
  • 公平锁/非公平锁
  • 可重入锁
  • 分段锁
  • 自旋锁
  • 分布式锁

1、乐观锁/悲观锁

  悲观锁/乐观锁体现的是一种思想,不是指具体什么类型的锁。

  悲观锁总是假设最坏的情况,每次拿数据都会上锁,这样共享资源只能被一个线程使用阻塞其他线程,当用完后才释放锁让其他线程可以使用锁。Java中的synchronizedReentrantLock就是悲观锁。数据库中的表锁和行锁也是悲观锁。

  乐观锁总是假设最好的情况,每次拿数据的时候总是认为别人不会修改。其实乐观锁就是不加锁,一般通过CAS算法(Compare and Swap)来保证操作正确。Java包java.util.concurrent.atomic下所有原子类都体现这种思想,如AtomicInteger.compareAndSet(int expect, int update),还有数据库操作update xxx set data=xxx, version=version+1 where id=xxx and version=xxx,在更新失败后可以重试。

3、独享锁/共享锁

  独享锁同时只能被一个线程获得,synchronized、ReentrantLock和ReentrantReadWriteLock.WriteLock就是独享锁。

  共享锁同时可以被多个线程获得。ReentrantReadWriteLock.ReadLock就是典型的共享锁。

4、公平锁/非公平锁

  公平锁就是所有线程按照先到先得的原则申请锁。

  非公平锁就是线程同时抢占锁,这样的坏处是可能导致某些线程始终无法申请到锁。

  ReentrantLock可以通过构造函数参数fair设定是否为公平锁,默认为非公平锁。ReentrantLock内部维护了一个队列,记录了每个线程申请锁的顺序,lock操作采用for循环的方式申请锁(compareAndSetState),对于非公平锁,所有线程同时尝试申请锁,公平锁则还要判断当前线程处于队列中的位置,当在它之前没有线程时才申请锁。ReentrantLock和synchronized都可以实现非公平锁,他们的不同在于ReentrantLock可以中断,可以设置超期时间,使用上更加灵活。

5、分段锁

  分段锁是一种设计,它不需要对操作对象整个加锁,分段锁将操作对象分成若干个区域,操作数据时我们先判断数据所在的区域,然后对这个区域加锁,防止其他线程操作这个区域的数据,通过又不影响其他区域数据的操作。Java中ConcurrentHashMap就实现了分段锁,每一个插槽都可以加锁。

6、分布式锁

   在单一系统中访问共享变量我们通过各种锁达到线程安全,但分布式系统中,系统间是独立,一个系统无法感知另外一个系统对共享数据的修改

   详细见:分布式锁的实现方式和优缺点&Java代码实现

7、如何检测一个线程是否拥有锁,使用Thread.holdsLock(),只能检测当前线程是否有锁

public class HoldsLockTest {
    Object o = new Object();

    @Test
    public void test() throws Exception {
        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (o) {
                    System.out.println("子线程是否有锁:" + Thread.holdsLock(o));
                }
            }
        }).start();
        System.out.println("主线程是否有锁:" + Thread.holdsLock(o));
        Thread.sleep(2000);
    }
}
原文地址:https://www.cnblogs.com/zhi-leaf/p/12821150.html