首页 > 其他分享 >JUC-locks锁

JUC-locks锁

时间:2024-11-13 13:48:18浏览次数:3  
标签:JUC 管程 ReentrantLock locks 线程 公平 final 资源

JUC-locks锁

如有侵权,请联系~
如有错误,也欢迎批评指正~

1、JUC-locks锁概述

java的并发包【JUC】下面就两个子包,一个是atomic原子包,另一个就是lock锁包。这个包下就提供了三种锁:ReentrantLock【可重入锁】、ReentrantReadWriteLock【可重入读写锁】、StampedLock【更轻量级的读写锁】。
这些锁的实现大部分都是基于AbstractQueuedSynchronizer【俗称的AQS,分两篇文章,防止难以消化】类实现的。

2、管程模型

管程模型是并发编程的万能钥匙,juc包下的锁以及阻塞队列几乎都是使用的管程模型实现的。当然管程模型【java选择】和信号量是【操作系统】等价的,这两个都是用来解决并发问题,以使用信号量实现管程,也可以使用管程实现信号量。
管程就是指管理共享变量,以及对共享变量的相关操作,对应于java中类的属性和方法。管程主要是三种模型:Hasen 模型、Hoare 模型和 MESA 模型,Java 使用的是 MESA 模型。
管程特点:

  • 封装性:管程将共享资源和操作这些资源的同步机制进行封装,共享变量只能被管程中的方法访问,外部过程不能访问。
  • 互斥访问:管程确保每次只有一个线程能够执行管程中的代码。当一个线程在管程中执行时,其他试图进入该管程的线程被阻塞,直到当前线程退出管程。
  • 条件变量:管程通常可以有一个或多个条件变量,用于线程间的条件同步。线程可以在管程内部等待某个条件成立(通过调用某个等待函数),同时释放锁,直到条件满足时得到通知。

管程模型的整体架构图:
在这里插入图片描述

3、ReentrantLock可重入锁

ReentrantLock可重入锁支持公平锁也支持非公平锁(具体实现可以查看各自的tryAcquire()方法),选择公平锁还是非公平锁根据创建该对象时候的参数。

ReentrantLock类内部有三个静态内部类Sync(NonfairSync和FairSync的基类)、NonfairSync(非公平锁)、FairSync(公平锁)
在看源码之前,看一下整体的架构图【根据上面的管程模型进行改动】:
在这里插入图片描述

3.1 ReentrantLock源码

public class ReentrantLock implements Lock, java.io.Serializable {
	private final Sync sync;
	
	// 在创建ReentrantLock对象的时候就指定是使用公平锁还是非公平锁。无参构造默认使用非公平锁。
	public ReentrantLock() {
	    sync = new NonfairSync();
	}
	public ReentrantLock(boolean fair) {
	    sync = fair ? new FairSync() : new NonfairSync();
	}
	
	// 调用内部静态类,根据参数是公平锁还是非公平锁
	public void lock() {
	    sync.lock();
	}

	// 不管这个锁是公平的还是非公平的,都是按照非公平锁的方式获取资源方法
	public boolean tryLock() {
	  return sync.nonfairTryAcquire(1);
	}
	
	// 调用AQS类的tryAcquireNanos,如果等待时间比较久超过1000ns,就会让该线程进行阻塞。
	// 这个会根据锁是公平锁还是非公平锁来获取资源,不同于tryLock()
	public boolean tryLock(long timeout, TimeUnit unit)
	  throws InterruptedException {
	  return sync.tryAcquireNanos(1, unit.toNanos(timeout));
	}
	
	public void unlock() {
	  sync.release(1);
	}
}

3.2 Sync静态内部类

abstract static class Sync extends AbstractQueuedSynchronizer {
  // 抽象方法,等待实现类NonfairSync和FairSync实现。该方法被ReentrantLock.Lock()调用
  abstract void lock();
  
  // 非公平锁获取资源。直接就获取资源,只看是不是有资源(资源被其他线程占用)
  final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
      // 如果资源没线程占用,则cas直接占用,占用成功直接返回,设置占用资源的线程为当前线程
      if (compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(current);
        return true;
      }
    }
    else if (current == getExclusiveOwnerThread()) {
      // 如果当前资源被占用,但是是被自己占用的,则可以继续占用【可重入】
      int nextc = c + acquires;
      if (nextc < 0) // overflow
        throw new Error("Maximum lock count exceeded");
      setState(nextc);
      return true;
    }
    return false;
  }
  
  // 这个方法其实是AQS中的方法,lock.unlock()就会调用这个方法
   public final boolean release(int arg) {
     if (tryRelease(arg)) {
       Node h = head;
       if (h != null && h.waitStatus != 0)
         unparkSuccessor(h);  // 释放完资源唤醒队列线程
       return true;
     }
     return false;
   }
  
  // 释放资源,释放资源不需要循环,只有获取资源才会竞争才需要CAS+自旋。释放不需要竞争
  protected final boolean tryRelease(int releases) {
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
      // 释放资源,前提肯定是当前线程占用资源。如果资源没被当前线程占用,当前线程释放肯定有问题
      throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
      free = true;
      setExclusiveOwnerThread(null);  // 如果释放所有资源,把owner线程释放
    }
    setState(c);
    return free;
  }
  
}

3.3 NonfairSync非公平锁

一句话概述:当一个线程想要获取资源的时候,先争取去获取资源state,如果没竞争到再老老实实去阻塞队列中排队。
那为什么阻塞队列中有等待线程,state资源还可能为空呢?
有可能上一个线程刚释放,阻塞线程还没来的急抢占。

static final class NonfairSync extends Sync {

  // 非公平锁在放入队列之前先判断是否可以获取到锁,如果能够获取到直接使用,否则加入阻塞队列
  final void lock() {
    if (compareAndSetState(0, 1))
       setExclusiveOwnerThread(Thread.currentThread());
    else
      acquire(1);  
  }

  protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
  }
}

3.4 FairSync公平锁

一句话概述:当前线程获取锁资源的时候,抢占资源的条件是阻塞队列中没有等待线程。

static final class FairSync extends Sync {
  // 直接加入阻塞队列
  final void lock() {
    acquire(1);
  }
  
  // 公平锁在获取资源的时候会先判断当前线程在队列中是否还存在前面的节点,即只有当前节点为第一个节点的时候才可以获取资源
  protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
      // 这里就体现了如果阻塞队列中有等待线程,即使当前资源空闲,也需要乖乖去排队。
      if (!hasQueuedPredecessors() &&
          compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(current);
        return true;
      }
    }
    else if (current == getExclusiveOwnerThread()) {
      int nextc = c + acquires;
      if (nextc < 0)
        throw new Error("Maximum lock count exceeded");
      setState(nextc);
      return true;
    }
    return false;
  }
}

通过上述发现公平锁和非公平锁最大的区别就是:

  • 不同点:公平锁即使当前资源空闲,只要阻塞队列中有线程等待就会乖乖去排队,而非公平锁则是只要资源闲着就去抢一下,抢不到再老老实实去阻塞队列排队。【公平锁像是一个懂得克制的乖孩子,非公平锁则是调皮捣蛋的小孩子,不管行不行,先抢他一下试试】
  • 相同点:
    • 非公平锁抢了一下没获取到资源,也需要去排队,只给你一次机会。
    • 公平锁和非公平锁调用tryLock()都是非公平锁实现,直接看资源能不能获取到。

标签:JUC,管程,ReentrantLock,locks,线程,公平,final,资源
From: https://blog.csdn.net/m0_50149847/article/details/143722054

相关文章

  • 一文彻底弄懂JUC工具包的Semaphore
    Semaphore是Java并发包(java.util.concurrent)中的重要工具,主要用于控制多线程对共享资源的并发访问量。它可以设置“许可证”(permit)的数量,并允许指定数量的线程同时访问某一资源,适合限流、资源池等场景。下面从源码设计、底层原理、应用场景、以及与其它JUC工具的对比来详......
  • 一文彻底弄懂JUC工具包的CountDownLatch的设计理念与底层原理
    CountDownLatch是Java并发包(java.util.concurrent)中的一个同步辅助类,它允许一个或多个线程等待一组操作完成。一、设计理念CountDownLatch是基于AQS(AbstractQueuedSynchronizer)实现的。其核心思想是维护一个倒计数,每次倒计数减少到零时,等待的线程才会继续执行。它的主要设......
  • JUC容器
    并发容器类这些类专为支持并发环境中的高效数据访问和操作而设计。与传统的容器类相比,并发容器类具有更好的线程安全性和性能。在使用多线程环境时,通常推荐使用这些并发容器以避免手动加锁和同步操作。ConcurrentHashMap特点:一个线程安全的哈希表,支持高效的并发访问。通过分......
  • 三个常见JUC辅助类
    三个常见JUC辅助类1.减少计数(CountDownLatch)​通过一个计数器来管理需要等待的线程数量,当这些线程都完成各自的任务后(即计数器递减到0),才会允许其他等待的线程继续执行。步骤:定义CountDownLatch类,并设置一个固定值在需要计数的位置加上countDown()方法使用await()......
  • KuangStudy-juc
    多线程进阶JUC1.什么是JUC三个包:java.util.concurrentjava.util.concurrent.atomicjava.util.concurrent.locks另外加上java.util.function2.线程和进程Java默认2个线程:main+GCJava可以开启线程吗?-不行->调用nativestart0()利用c++调用底层资源并发、并行并......
  • juc复习(下篇)(10.31)
    juc复习(10.31)阻塞队列写入:如果队列满了,就必须阻塞等待读取:如果队列是空的,必须阻塞等待生产使用阻塞队列的情况多线程并发处理,线程池四组API方式抛出异常有返回值不抛出异常阻塞等待超时等待添加addofferputoffer(3个参数)移除removepolltakepoll(两个参数)检测队首元素e......
  • JUC并发编程1
    JUC并发编程1.常见的加锁方式1.1synchronized关键字要求:多个线程并行执行,依次实现对数字的+1、-1操作。即一次+1、一次-1,依次执行。Share类classShare{privateintnumber=0;publicsynchronizedvoidincr()throwsInterruptedException{//......
  • LockSupport
    LockSupport是什么LockSupport是用来创建锁和其他同步类的基本线程阻塞原语,其中park()和unpack()而作用分别是阻塞线程和解除阻塞线程.线程等待唤醒机制三种让线程等待和唤醒的方法方式一:使用Object中的wait()方法让线程等待,使用Object中的notify()方法唤醒线程方式二:使用J......
  • 你了解JUC吗
    1.什么是JUC1.1JUC简介JUC(JavaUtilConcurrent)是Java中的一个并发工具包,提供了一系列用于多线程编程的类和接口,旨在简化并发编程并提高其效率和可维护性。JUC库包含了许多强大的工具和机制,用于线程管理、同步和协调。1.2并发与并行并发和并行的区别1.并发早期计算......
  • mysql 1206 - The total number of locks exceeds the lock table size
    由于数据量过大导致报错:Thetotalnumberoflocksexceedsthelocktablesize解决方法:输入查询:showvariableslike"%_buffer%";找到对应的 innodb_buffer_pool_size 默认值是8388608  8兆将这个数值设置的大一点,比如1G1G=1024*1024*1024=1073741824 setGLOB......