java线程和锁

article/2025/10/20 18:50:45
锁,是计算机系统中非常常见的技术,实现线程对资源的独占,防止对资源的并发读写造成错误,本文通过从线程,线程状态,到java提供的锁基础,基础的复盘一下线程和锁

线程

计算机系统中,经常听到线程和进程的概念。
进程:一个被编译好的程序,被系统加载到内存中,开始运行时,就产生了一个该程序的进程。在进程结束前,该程序将占有内存的一部分空间,作为程序运行空间和环境。进程是一种静态的概念,指程序运行时所占有的一些计算机资源,内存 网络等资源;
线程:产生程序进程的过程中,进程都会产生一条主线程,java一般是main方法。线程主要是对计算机cpu计算的占用,计算机cpu在多个线程之间做切换,来处理线程中的程序指令。线程是一种动态的概念,是指程序对系统cpu计算功能的占用,与进程能较长时间稳定存在与内存中不同,线程的创建销毁比较频繁,程序用户的一个个请求,将会在服务器上产生很多的进程(一般情况下,都会有线程池,线程也会被复用,减少新建线程的开销)。

jvm的线程

Thread thread = new Thread();

线程的状态

        public enum State { 从上到下值 从 05NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;}

线程状态流转
线程状态流转
状态waiting(等待) 和 blocked(阻塞)的区别

两者都表示线程当前暂停执行的状态,而两者的区别,基本可以理解为:进入 waiting 状态是线程主动的,而进入 blocked 状态是被动的。
更进一步的说,进入 blocked 状态是在同步(synchronized)代码之外,而进入 waiting 状态是在同步代码之内(然后马上退出同步)。
wait 和 notify 只能在 同步代码块中执行 synchronized
阻塞 blocked 和 waiting 的区别, ed 和 ing 时态,代表 blocked是被动进入,waiting是主动进入
blocked 是在抢 synchronized 同步锁失败之后的(同步代码块之外),一种被迫的状态,没抢到锁,blocked 线程让出cpu资源,但是后续如果synchronized 锁被其他线程释放,则被jvm唤醒直接进入running状态,waiting 是在 调用object.wait之后进入的状态(同步代码块之中),是线程自己调用object.wait方法,是自己进入的状态,自己被定住了,则需要别的线程调用 object.notify 解锁,(主动进入,被别的线程唤醒),线程调用object.wait方法,会释放 synchronized 锁,也必须释放,不然别的线程不能进入同步代码块中解锁会造成死锁(造成我自己锁我自己,我自己又锁别人,而我解锁我自己需要别人帮忙,别人又被我锁外面了造成死锁)

  1. 为什么 waiting 和 notify notifyAll 只能在 synchronized 中使用,为什么总是让 waiting 和 notify 原子操作
    因为jvm 希望 waiting 和 notify 要保持原子性,防止出现死锁的情况

    //有序性 -》 指令重排,代码乱序执行;   synchronized语义就要求线程在访问读写共享变量时只能“串行”执行  volatile  synchronized
    //原子性 -》 一个线程对一段代码的执行,要么不执行,要么全部执行完毕      synchronized
    //可见性 -》 多个线程操作同一快内存对象,各线程的工作内存中该值可能不同  volatile  synchronized
    

// synchronized: 具有原子性,有序性和可见性;
// volatile:具有有序性和可见性
// AQS.lock:具有原子性,有序性和可见性;

线程中断

thread.interrupt()

// 具体来说,当对一个线程,调用 interrupt() 时,
//  ① 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。仅此而已。
//  ② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为 true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。
//
// interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。
// 也就是说,一个线程如果有被中断的需求,那么就可以这样做。
//  ① 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
//  ② 在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程。

重入锁 ReentrantLock

//since 1.5
//java.util.concurrent.locks.ReentrantLock
ReentrantLock lock = new ReentrantLock();

ReentrantLock ,是jdk 1.5提供的一种更加灵活的锁机制,是AQS的一种实现
(AQS全名:AbstractQueuedSynchronizer,是并发容器J.U.C(java.util.concurrent)下locks包内的一个类。它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列。底层实现的数据结构是一个双向链表。),相比synchronized,他提供了更多功能扩展,场景引用更多样;
她的特性,主要有如下几点

  1. 排他性,锁要实现对一个资源的独占
  2. 等待,要实现未拿到锁线程的存储排队,并让线程等待
  3. 可重入,以拿到拿到锁钥匙的线程,再次进入时不等待直接进入,防止死锁
  4. 释放,使用完毕后释放资源,允许其他线程进入
  5. 等待超时

我们通过源码来了解ReentrantLock 如何实现如上的特性
ReentrantLock.NonfairSync 和ReentrantLock.FairSync的父类 AQS 实例有几个关键的成员变量

  • Thread exclusiveOwnerThread; 独占此锁的线程对象(这个是实现重入的关键)
  • int state; state是标志位,为0是锁空闲,1为锁被占;lock时调用, compareAndSetState(0, 1) ,如上确定当前线程是获取锁还是去排队
  • Node head; Node tail; AQS 提供的链表用来存储 等待中的线程
    ReentrantLock 的锁实现也是基于如上AQS的几个关键 成员变量实现
    AQS中线程队列

ReentrantLock 内部实现了两种锁 公平锁和非公平锁,默认使用非公平锁

    public ReentrantLock() {sync = new ReentrantLock.NonfairSync();}public ReentrantLock(boolean fair) {sync = fair ? new FairSync() : new NonfairSync();}

lock.lock(),实现对代码块的加锁,我们通过lock的调用,分析其如何实现锁的功能

        // NonfairSync 非公平锁lock的实现final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}// 如果锁空闲, 将state 设置为 1,将当前线程设置为 exclusiveOwnerThread 独裁者线程// 如果锁占用, 调用 acquire 等待获取,无法抢占,其他线程必须去排队// acquire 是 AQS 的实现public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg))selfInterrupt();}}// tryAcquire AQS 的实现是 直接抛出异常,可见虽然AQS提供了队列排队的机制,但是其默认的处理是让没抢到锁的线程直接抛异常protected boolean tryAcquire(int arg) {throw new UnsupportedOperationException();}   //ReentrantLock.NonfairSync.tryAcquire  //ReentrantLock 要实现等待机制 肯定不能直接抛异常//NonfairSync 重写了 tryAcquire,将没抢到lock的线程加入队列中protected final boolean tryAcquire(int acquires) {return nonfairTryAcquire(acquires);}final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();// 贼心不死,临近队列之前 再询问锁是否空闲,c==0则自己不用等待 直接占有锁 线程不等待int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}else if (current == getExclusiveOwnerThread()) {// 可重入锁的关键,虽然锁被占,但占锁的 有没有可能是我自己,如果是我自己,当然直接进入,不等待int nextc = c + acquires;// 当前 state +1 重入也是有限制的 不断的重入加一 nextc <0 其实是 state 大于int最大值之后出现了,这么大的重入情况,一定是当前线程代码有bug了,没有解锁导致的// ReentrantLock 在此加判断 防止一个线程不断重入导致死锁if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}	//书接上文 AQS !tryAcquire(arg) &&acquireQueued(addWaiter(AbstractQueuedSynchronizer.Node.EXCLUSIVE), arg)//上文讲了 tryAcquire 主要是再次判断是否有锁和最大加锁判断//acquireQueued 则是让线程进入等待 和 进入链表的 关键方法// addWaiter  将thread 封装成 node 节点,可以看到 将新的线程 加到了链表的尾部 (进入链表)private AbstractQueuedSynchronizer.Node addWaiter(AbstractQueuedSynchronizer.Node mode) {AbstractQueuedSynchronizer.Node node = new AbstractQueuedSynchronizer.Node(Thread.currentThread(), mode);// Try the fast path of enq; backup to full enq on failureAbstractQueuedSynchronizer.Node pred = tail;if (pred != null) {node.prev = pred;if (compareAndSetTail(pred, node)) {pred.next = node;return node;}}enq(node);return node;}// 加入链表,再次判断是否当前锁是否空闲final boolean acquireQueued(final AbstractQueuedSynchronizer.Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final AbstractQueuedSynchronizer.Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}//多重的询问锁是否空闲之后,还是没抢到,线程就进入的等待的队列,此时 要让此线程等待,释放CPU计算资源selfInterrupt();static void selfInterrupt() {Thread.currentThread().interrupt();}

如上就是lock.lock()的执行流程,我们最终了lock的执行联调, 排他性和等待,可重入 已经实现

lock.unlock(),解锁

        public void unlock() {sync.release(1);}//解锁是 调用tryRelease 校验public final boolean release(int arg) {if (tryRelease(arg)) {AbstractQueuedSynchronizer.Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}// 解锁线程 == 占锁线程 可以执行解锁操作,否则报错//1    -》 如果此锁没被重入过 那就是 一lock 一 unlock  c = 0,故为0时直接将独占线程设为了null 修改state为0 返回true 准备唤醒下一个等待的线程//2    -》 如果锁有重入 则 c > 0,故本次调 lock 只是将 state -1 ,无法释放锁,待 state == 0 才能解锁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);}setState(c);return free;}// tryRelease 返回 true,代表 修改state为0,独占线程设为了null,锁已经释放,但是,等待着的线程 还需要唤醒,即要处理链表里的线程// 调用 unparkSuccessor// 取出链表的下一个节点//   链表的下一个节点 为null,表明解锁时,没有线程正在排队,// *//   也有一种可能,就是已经有线程进入//   这里要说一下 ReentrantLock 默认非公平锁,新线程是会判断state 如果为0 直接插队,占有锁的,不会一个人跑到链表尾部,所以//   因为有插队的存在,所有 下一个节点 不一定 waitStatus 为 0,可能早在你执行tryRelease 代码时,有人插队拿锁了//  排除以上的情况,则可以 LockSupport.unpark(s.thread); 唤醒下一个节点中的线程,让他从阻塞 变为 运行态private void unparkSuccessor(AbstractQueuedSynchronizer.Node node) {AbstractQueuedSynchronizer.Node s = node.next;if (s == null || s.waitStatus > 0) {s = null;for (AbstractQueuedSynchronizer.Node t = tail; t != null && t != node; t = t.prev)if (t.waitStatus <= 0)s = t;}if (s != null)//唤醒线程LockSupport.unpark(s.thread);}

如上 unlock 先释放了锁,然后去唤醒链表的下一个节点线程,让别人继续执行。

等待超时
ReentrantLock 类的 lock 是没法设置超时的,即如果拿不到锁 就进入队列排队,不会因为太久拿不到锁,抛出异常。
可以用ReentrantLock 如下两个api来代替,防止线程拿锁太久,导致无响应

  • tryLock(),拿锁,拿到就直接占有锁,拿不到就返回false,不排队,让线程去做别的操作
  • tryLock(long timeout, TimeUnit unit) ,拿锁,拿不到就等待一定时间 还是拿不到就返回false,让线程去做别的操作

如上也能实现超时处理,为了避免一直拿不到锁 导致的线程等待太久问题,可以使用 tryLock 方法

总结,一套api下来,ReentrantLock 基本实现了 我们所需要锁的几大特性。


http://chatgpt.dhexx.cn/article/OxpxbpxP.shtml

相关文章

Java线程里的14种锁

参考资料: 不可不说的Java“锁”事 java多线程的15种锁 以下内容是参考上面两片文章写出的粗略总结, 想要细究可以看上面两位大佬写的文章, 由于是参考着写的, 所以有很多地方相同, 如果有侵权或不妥的地方还请联系删除. 一. 线程是否同步资源? 1. 悲观锁 : 同步 每次拿数…

Java多线程 各种锁(一篇全搞懂)

Java多线程 锁 文章目录 Java多线程 锁1、乐观锁与悲观锁2、公平锁与非公平锁3、可重入锁与不可重入锁4、独享锁与共享锁5、自旋锁 VS 适应性自旋锁6、无锁 、 偏向锁、量级锁 和 重量级锁&#xff08;难点&#xff09; 1、乐观锁与悲观锁 &#xff08;1&#xff09;悲观锁 对…

Java多线程编程(三)——线程锁

卖票案例 同步代码块解决数据安全问题 同步方法解决数据安全问题 同步方法的格式&#xff1a; 同步方法和同步方法块的区别&#xff1a; 同步静态方法 Lock锁 卖票案例 某电影院目前正在上映国产大片&#xff0c;共有30张票&#xff0c;而它有3个窗口卖票&#xff0c;请…

浅析Java 多线程中的锁

前言 随着互联网技术的快速发展&#xff0c;多线程编程已经成为了现今编程领域中必不可少的知识点之一。Java 是一种广泛使用的编程语言&#xff0c;也是一些底层应用程序和高并发应用程序的首选语言。而 Java 提供的多线程编程机制和相关的锁机制&#xff0c;则成为了 Java 开…

【Java多线程进阶】常见的锁策略

前言 众所周知&#xff0c;拳击运动员是要分等级&#xff08;轻量级、重量级等等&#xff09;来参加比赛的&#xff0c;在 Java 多线程中 锁&#xff08;synchronized&#xff09; 也会根据锁的竞争程度来升级为相关“高等级”锁&#xff0c;为了更好的理解 synchronized 加锁机…

Java多线程下——各类锁的详解

这里写目录标题 各类锁的详解常见的锁策略乐观锁 vs 悲观锁读写锁重量级锁 vs 轻量级锁自旋锁&#xff08;Spin Lock&#xff09;公平锁 vs 非公平锁可重入锁 vs 不可重入锁 CASSynchronized 原理偏向锁锁消除锁粗化 Callable 接口ReentrantLock线程池ExecutorService 和 Execu…

Java中的线程和锁机制

线程池 为什么使用线程池&#xff1f;线程池执行原理&#xff1f;线程池参数有哪些&#xff1f;线程池大小怎么设置&#xff1f;线程池的类型有哪些&#xff1f;适用场景&#xff1f; 进程线程 线程的生命周期讲一下线程中断&#xff1f;创建线程有哪几种方式&#xff1f;什么是…

【Java】中的多线程线程锁

多线程 文章目录 多线程线程的创建和启动sleep()stop() 线程的休眠和中断线程的优先级线程的礼让和加入yield()stop() 线程锁和线程同步synchronized 关键字 死锁概念 wait & notify methodThreadLocal的使用定时器 Timer守护线程再谈集合类parallelStreamforEachOrdered()…

Java多线程中 的各种锁

学习 java 多线程时&#xff0c;最头疼的知识点之一就是 java 中的锁了&#xff0c;什么互斥锁、排它锁、自旋锁、死锁、活锁等等&#xff0c;细分的话可以罗列出 20 种左右的锁&#xff0c;光是看着这些名字就足以让人望而却步了&#xff0c;更别说一个个去理解它们的含义了。…

Java——多线程和锁

多线程 前言:当我们打开一个网站时&#xff0c;不同部分的加载并不是先后出现的&#xff0c;是并行出现的&#xff0c;没有出现一个地方没加载完&#xff0c;别的地方就也加载不出来这种事。这个就是多线程并行运行。 当其中一个线程发生阻塞时&#xff0c;操作系统会自动执行…

Java-多线程中的“锁“

文章目录 Java多线程中的锁1. 什么是锁&#xff1f;2. 锁的作用3. 锁的类型4. 锁的使用示例5.乐观锁和悲观锁6. 锁的注意事项总结 Java多线程中的锁 在Java多线程编程中&#xff0c;锁是一种重要的同步机制&#xff0c;用于保护共享资源的访问。使用锁可以防止多个线程同时对共…

JAVA三种线程锁

内置锁&#xff1a;synchriozed&#xff0c;关键字&#xff0c;同步代码块&#xff0c;object.wait和object.notify/notifyall 显示锁&#xff1a;Lock&#xff0c;JUC包下的类&#xff0c;同步代码块&#xff0c;condition.await和condition.signal/signalall 原子类&#xff…

Java多线程中锁的理解与使用

1.简介 锁作为并发共享数据&#xff0c;保证一致性的工具&#xff0c;在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等 ) 。 2.Java锁的种类 公平锁/非公平锁可重入锁独享锁/共享锁互斥锁/读写锁乐观锁/悲观锁分段锁偏向锁/轻量级锁/重量级锁自旋锁 上面是很多锁…

java多线程的15种锁

1 java锁分类 下面我们依照序号依次的介绍每一种锁 2 悲观锁和乐观锁 悲观锁和乐观锁是一种广义的概念&#xff0c;体现的是看待线程同步的不同的角度 悲观锁认为自己在使用数据的时候&#xff0c;一定有别的线程来修改数据&#xff0c;在获取数据的时候会先加锁&#xff0c…

Java多线程 - 锁

Java多线程 - 锁 三性 可见性 指的是线程之间的可见性&#xff0c;一个线程对状态的修改&#xff0c;对其他线程是可见的。在 Java中 volatile、synchronized 和 final 实现可见性。 原子性 如果一个操作是不可分割的&#xff0c;我们则称之为原子操作&#xff0c;也就是有原…

Java多线程与锁

前文中&#xff0c;我们已经了解了什么是线程&#xff0c;线程间常用通信方式&#xff0c;线程池以及其相关特性&#xff0c;可以看出锁在多线程环境中充当着重要作用&#xff0c;不管是线程间的数据通信&#xff0c;还是线程间的等待和唤醒&#xff0c;都依赖于锁&#xff0c;…

JAVA基础-多线程中锁机制

多线程锁 多线程锁机制锁的定义锁的分类公平锁/非公平锁可重入锁独享锁/共享锁互斥锁/读写锁乐观锁/悲观锁分段锁偏向锁/轻量级锁/重量级锁自旋锁 锁的使用AQSAQS框架展示AQS定义两种资源共享方式AQS常用的几种方法&#xff08;自定义同步器实现时&#xff09;自定义同步器实现…

多线程系列-Java中的锁(简介)

前言 Java提供了种类丰富的锁&#xff0c;每种锁因其特性的不同&#xff0c;在适当的场景下能够展现出非常高的效率。本文旨在对锁相关源码&#xff08;本文中的源码来自JDK 8和Netty 3.10.6&#xff09;、使用场景进行举例&#xff0c;为读者介绍主流锁的知识点&#xff0c;以…

JAVA如何在线程中加锁(四种方法)

JAVA多线程锁 线程的生命周期 ​ 总共六种状态&#xff0c;可归结为五种&#xff0c;线程的最终是死亡&#xff0c;阻塞不是最终状态&#xff0c;只是一个临时状态。只有调用了start方法&#xff0c;线程才进入就绪阶段。 //新生 ​ NEW, //运行​ RUNNABLE, //阻塞​ BLOCKE…

大数据平台_大数据应用场景有哪些

大数据时代的出现&#xff0c;简单的讲是海量数据同完美计算能力结合的结果&#xff0c;确切的说是移动互联网、物联网产生了海量的数据&#xff0c;大数据计算技术完美地解决了海量数据的收集、存储、计算、分析的问题。一些公司也成立了大数据部门&#xff0c;大数据得到了企…