Java中常见的锁的使用


锁的分类

在Java中锁根据不同的标准进行了划分,常见的类别是:

  • 基于锁的并发控制方式分类:悲观锁/乐观锁
  • 基于锁的等待方式和性能调整策略分类:自旋锁/适应性自旋锁
  • 基于锁的获取顺序和竞争机制分类:公平锁/非公平锁
  • 基于锁的可重入性分类:可重入锁/非可重入锁
  • 基于锁的访问权限和并发控制策略分类:共享锁/独享锁
  • 基于锁的具体实现方式和性能特征分类:无锁/偏向锁/轻量级锁/重量级锁

为对于Java中的锁而言,一把锁也可能占有多种标准,符合多种分类,比如ReentrantLock既可以是悲观锁,也可以是可重入锁。


一、悲观锁/乐观锁

  1. 解释:
  • 悲观锁:采用的是一种先获取锁在访问的策略,每次在读写数据的时候都会上锁,事务结束之后,自动释放锁。
  • 乐观锁:获取数据的时候并不会加锁,只有更新的时候判断是否有人修改数据(可以通过版本号对比),如果无则正常更新,否则重复读-比较-写的操作。
  1. 适用场景:
  • 悲观锁适合写操作多的场景,先加锁可以保证写操作数据的正确性
  • 乐观锁适合读操作多的场景,不加锁的特点可以提高读的性能

常见的悲观锁有:synchronized、ReentrantLock、还有数据库的for update方法。

3.示例:


    //ReentrantLock 悲观锁
    private ReentrantLock lock = new ReentrantLock();
    private void testReentrantLock() {
        lock.lock();
        System.out.println(Thread.currentThread().getName() + "进来了。。。");
        lock.unlock();
    }
   //synchronized 悲观锁
   private synchronized void testSynchronized() {
        //doSomething
        System.out.println(Thread.currentThread().getName() + "进来了。。。");
    }
    //乐观锁的调用方式
    private AtomicInteger atomicInteger = new AtomicInteger();
    private void testAtomicInteger(){
        //执行自增+1
        atomicInteger.incrementAndGet();
    }

通过上述示例,我们可以很清晰的看到,悲观锁是先实现锁定,再操作同步资源,而乐观锁则直接操作同步资源

二、自旋锁/适应性自旋锁

1.自旋锁/适应性自旋锁 解释:

  • 自旋锁:当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
  • 适应性自旋锁:在JDK1.6引入自适应自旋锁,自适应自旋锁是对自旋锁的改进,可以根据锁的争用状态和系统负载动态的调整等待时间,以便在锁定时间较短的情况下执行自旋。

2.自旋锁 使用示例:


//自旋锁
public class SpinLock {
    //初始化锁的状态变量为0
    private AtomicInteger state = new AtomicInteger(0);
    public void lock() {
        //判断如果state变量值如果是0,则获取锁,否则一直循环自旋,直到变量为0,获取锁
        while (!state.compareAndSet(0, 1)) {
            // 自旋等待,直到成功获取锁
            System.out.println(Thread.currentThread().getName() + " 自旋中。。。。");
        }
    }
    public void unlock() {
        state.set(0); // 释放锁
    }

    public static void main(String[] args) {
        SpinLock spinLock = new SpinLock();
        Runnable task = () -> {
            spinLock.lock();
            try {
                System.out.println(Thread.currentThread().getName() + " 获取锁 "+ spinLock.state.get());
                //sleep是为了模拟锁持有中,需要时间释放
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                spinLock.unlock();
                System.out.println(Thread.currentThread().getName() + " 释放锁 "+spinLock.state.get());
            }
        };

        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);

        thread1.start();
        thread2.start();
    }
}

打印结果:
在这里插入图片描述

三、公平锁/非公平锁

1.解释:

  • 公平锁:每个线程机会平等,竞争锁的时候,需要排队,先来先得。
  • 非公平锁:它允许后来的线程在早期线程之前获取锁,不需要排队,直接尝试获取锁,获取不到锁时在到队尾排队。

2.示例:

在Java中,我们可以使用ReentrantLock来创建公平锁或非公平锁,我们来看下具体创建方式:

ReentrantLock fairLock = new ReentrantLock(true); // 创建公平锁
ReentrantLock unfairLock = new ReentrantLock(false); // 创建非公平锁

使用ReentrantLock来创建公平锁或非公平锁的差别只是在于ReentrantLock的入参是true,还是false。
如果是true 则创建的就是公平锁,false 则为非公平锁,而ReentrantLock的无参构造默认使用的是非公平锁。

四、可重入锁/非可重入锁

1.解释:

  • 可重入锁:它允许同一个线程在已经获取锁的情况下,再次获取同一个锁,也就意味着一个线程可以多次嵌套的调用同步方法,而不会引起死锁。
  • 非可重入锁:非可重入锁不允许同一线程多次获得同一个锁,以避免死锁。如果一个线程已经获得了该锁,尝试再次获取该锁将会被拒绝,从而避免嵌套锁定。

2.示例:
在Java中通常不会直接使用非重入锁,我们暂且不聊,我们熟知ReentrantLock和synchronized都是重入锁,简单示例如下:


public class TestReentrantLock {
    public static synchronized void doSomething() {
        System.out.println("方法1执行...");
        doOthers();
    }
    public static synchronized void doOthers() {
        System.out.println("方法2执行...");
    }
    public static void main(String[] args) {
        doSomething();
    }
}


打印结果:
在这里插入图片描述

五、共享锁/独享锁

1.解释:

  • 共享锁:允许多个线程同时获取锁,并发访问共享资源。
  • 独享锁:每次只允许一个线程持有该锁。在该线程未释放锁之前,其他线程无法再次获取锁。

ReentrantReadWriteLock类中有两把锁,ReadLock代表共享锁,WriteLock表示独享锁。

2.独享锁示例:

private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

public static void main(String[] args) {
    int numThreads = 4;
    for (int i = 0; i < numThreads; i++) {
        Thread writeThread = new Thread(() -> {
            writeSharedData();
        });
        writeThread.start();
    }
}

public static void writeSharedData() {
      lock.writeLock().lock(); // 获取独享锁
      try {
          System.out.println(Thread.currentThread().getName() + " 获取锁,时间纳秒:" + System.nanoTime());
          // 模拟写入操作
          Thread.sleep(5000);
      } catch (InterruptedException e) {
          throw new RuntimeException(e);
      } finally {
          lock.writeLock().unlock(); // 释放独享锁
          System.out.println(Thread.currentThread().getName() + " 释放锁,时间纳秒:" + System.nanoTime());
      }
 }

打印结果:

Thread-0 获取锁,时间纳秒:31828569097319
Thread-1 获取锁,时间纳秒:31833586428212
Thread-0 释放锁,时间纳秒:31833586294181
Thread-1 释放锁,时间纳秒:31838588677388
Thread-2 获取锁,时间纳秒:31838588710856
Thread-2 释放锁,时间纳秒:31843593734731
Thread-3 获取锁,时间纳秒:31843593791252
Thread-3 释放锁,时间纳秒:31848595048304

可以看到只有当上一个线程的锁释放之后,下一个线程才能获取到锁

3.共享锁示例:

private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

public static void main(String[] args) {
    int numThreads = 4;
    for (int i = 0; i < numThreads; i++) {
        Thread writeThread = new Thread(() -> {
            readSharedData();
        });
        writeThread.start();
    }
}

public static void readSharedData() {
    lock.readLock().lock(); // 获取共享锁
    try {
        System.out.println(Thread.currentThread().getName()+" 获取锁,时间纳秒:" + System.nanoTime() );
        //模拟读取操作
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    } finally {
        lock.readLock().unlock(); // 释放共享锁
        System.out.println(Thread.currentThread().getName()+" 释放锁,时间纳秒:" + System.nanoTime());
    }
}

打印结果:

Thread-2 获取锁,时间纳秒:32333519016367
Thread-0 获取锁,时间纳秒:32333518371762
Thread-1 获取锁,时间纳秒:32333518999821
Thread-3 获取锁,时间纳秒:32333518998279
Thread-2 释放锁,时间纳秒:32343543747372
Thread-0 释放锁,时间纳秒:32343543705526
Thread-1 释放锁,时间纳秒:32343543748094
Thread-3 释放锁,时间纳秒:32343543773606

共享锁并没有等待上一个线程释放锁才持有,而是多个线程共享的。

六、无锁/偏向锁/轻量级锁/重量级锁

在 Java中,”无锁/偏向锁/轻量级锁/重量级锁“ 这些状态通常是用来描述synchronized关键字锁的状态。

级别从低到高:无锁->偏向锁->轻量级锁->重量级锁,锁的状态只能升级不能降级。

  • 无锁:没有对资源进行锁定,所有线程都可以访问并修改同一资源,但同时只有一个线程修改成功。
  • 偏向锁:如果不存在多线程竞争,当一个线程获取锁,就进入偏向模式,称为偏向锁,可以一直持有,不会主动释放偏向锁,在其他线程需要竞争锁的时候,释放,撤销偏向锁后恢复到未锁定,或者轻量级锁的状态。
  • 轻量级锁:是指当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。
  • 重量级锁:当自旋超过一定的次数,或者一个线程在持有锁,一个线程在自旋,又有第三个来访时,轻量级锁升级为重量级锁,此时等待锁的线程都会进入阻塞状态。
  • 15
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值