多线程进阶学习03------锁概念详解

简介: 多线程进阶学习03------锁概念详解

要保障共享变量的线程安全,最熟知的解决办法就是加锁,但是锁在使用上很简单就是加一个关键字Synchronized,但是这个锁性能太差了。为了降低加锁带来的性能损耗,锁被分为了各种各样的分类,根据业务场景不同选用合适的锁,增加系统吞吐量。

按概念分类

悲观锁与乐观锁

悲观锁

悲观锁总是假设最坏的情况,认为共享资源每次被访问的时候就会出现问题(比如共享数据被修改),所以每次在获取资源操作的时候都会上锁,这样其他线程想拿到这个资源就会阻塞直到锁被上一个持有者释放。

也就是说,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程。

像 Java 中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

乐观锁

乐观锁总是假设最好的情况,认为共享资源每次被访问的时候不会出现问题,线程可以不停地执行,无需加锁也无需等待,只是在提交修改的时候去验证对应的资源(也就是数据)是否被其它线程修改了(具体方法可以使用版本号机制或 CAS 算法)。

在 Java 中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式 CAS 实现的。

可重入锁(递归锁)

就是一个线程不用释放,可以重复的获取一个锁n次,只是在释放的时候,也需要相应的释放n次。(简单来说:A线程在某上下文中或得了某锁,当A线程想要在次获取该锁时,不会应为锁已经被自己占用,而需要先等到锁的释放)假使A线程即获得了锁,又在等待锁的释放,就会造成死锁。synchronizedreentrantlock都是可重入锁

写锁(独占)、读锁(共享)

写锁(独占)

独占锁也叫排他锁、互斥锁、独享锁,是指锁在同一时刻只能被一个线程所持有。一个线程加锁后,任何其他试图再次加锁的线程都会被阻塞,直到持有锁线程解锁。通俗来说,就是共享资源某一时刻只能有一个线程访问,其余线程阻塞等待。


如果是公平地独占锁,在持有锁线程解锁时,如果有一个以上的线程在阻塞等待,那么最先抢锁的线程被唤醒变为就绪状态去执行加锁操作,其他的线程仍然阻塞等待。


java中的Synchronized内置锁和ReentrantLock显式锁都是独占锁。


写锁也相当于独占锁,当有一个线程去写,其他线程就必须获取锁才可以执行代码。读锁(共享)

共享锁就是在同一时刻允许多个线程持有的锁。当然,获得共享锁的线程只能读取临界区的数据,不能修改临界区的数据。

JUC中的共享锁包括Semaphore(信号量)、ReadLock(读写锁)中的读锁、CountDownLatch。

而读锁就是一种共享锁,当多个线程去读取时,可以都获取到锁并执行读操作。

公平、非公平锁

公平锁

是指多个线程按照申请锁的顺序来获取锁,类似于排队买饭,先来后到,先来先服务,就是公平的,也就是队列

非公平锁

是指多个线程获取锁的顺序,并不是按照申请锁的顺序,有可能申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转,或者饥饿的线程(也就是某个线程一直得不到锁)


公平锁和非公平锁的创建 并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或者非公平锁,默认是非公平锁

死锁

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁

产生死锁的原因

  1. 系统资源不足
  2. 进程运行推进的顺序不合适
  3. 资源分配不当

代码展示

package com.bilibili.juc.job;
import java.util.concurrent.TimeUnit;
public class Test {
    int i = 0;
    Object lock1 = new Object();
    Object lock2 = new Object();
    public void a() throws InterruptedException {
        synchronized (lock1) {
            TimeUnit.SECONDS.sleep(1);
            synchronized (lock2) {
                System.out.println("a");
            }
        }
    }
    public void b() throws InterruptedException {
        synchronized (lock2) {
            TimeUnit.SECONDS.sleep(1);
            synchronized (lock1) {
                System.out.println("b");
            }
        }
    }
    public static void main(String[] args) {
        Test test = new Test();
        new Thread(() -> {
            try {
                test.a();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(() -> {
            try {
                test.b();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

jstack命令排查

最后一行发现了死锁

PS E:\学习目录\java系列\并发编程\juc_bilibili> jps
25492 Test
27060 Launcher
25848 
22316 
25964 RemoteMavenServer36
3196 Jps
PS E:\学习目录\java系列\并发编程\juc_bilibili> jstack 25492
2023-03-20 14:47:10
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.162-b12 mixed mode):
"DestroyJavaVM" #24 prio=5 os_prio=0 tid=0x0000000003925000 nid=0x1144 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"Thread-1" #23 prio=5 os_prio=0 tid=0x0000000034c86800 nid=0x6698 waiting for monitor entry [0x000000003541f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.bilibili.juc.job.Test.b(Test.java:26)
        - waiting to lock <0x00000006c1b226d0> (a java.lang.Object)
        - locked <0x00000006c1b226e0> (a java.lang.Object)
        at com.bilibili.juc.job.Test.lambda$main$1(Test.java:43)
        at com.bilibili.juc.job.Test$$Lambda$2/88558700.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"Thread-0" #22 prio=5 os_prio=0 tid=0x0000000034c32800 nid=0x760 waiting for monitor entry [0x000000003531e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.bilibili.juc.job.Test.a(Test.java:17)
        - waiting to lock <0x00000006c1b226e0> (a java.lang.Object)
        - locked <0x00000006c1b226d0> (a java.lang.Object)
        at com.bilibili.juc.job.Test.lambda$main$0(Test.java:35)
        at com.bilibili.juc.job.Test$$Lambda$1/128526626.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"Service Thread" #21 daemon prio=9 os_prio=0 tid=0x0000000031f60800 nid=0x6c6c runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C1 CompilerThread11" #20 daemon prio=9 os_prio=2 tid=0x0000000031e91800 nid=0x7310 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C1 CompilerThread10" #19 daemon prio=9 os_prio=2 tid=0x0000000031e90800 nid=0x6c48 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C1 CompilerThread9" #18 daemon prio=9 os_prio=2 tid=0x0000000031e97800 nid=0x678c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C1 CompilerThread8" #17 daemon prio=9 os_prio=2 tid=0x0000000031e80000 nid=0x4a4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread7" #16 daemon prio=9 os_prio=2 tid=0x0000000031e76000 nid=0x5a34 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread6" #15 daemon prio=9 os_prio=2 tid=0x0000000031e74800 nid=0x6c50 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread5" #14 daemon prio=9 os_prio=2 tid=0x0000000031e6b800 nid=0x497c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread4" #13 daemon prio=9 os_prio=2 tid=0x0000000031e69000 nid=0x190 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread3" #12 daemon prio=9 os_prio=2 tid=0x0000000031e5e000 nid=0x5e88 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #11 daemon prio=9 os_prio=2 tid=0x0000000031e5d800 nid=0x6d1c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #10 daemon prio=9 os_prio=2 tid=0x0000000031e5c800 nid=0x6c78 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #9 daemon prio=9 os_prio=2 tid=0x0000000031e5b000 nid=0x60f0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"JDWP Command Reader" #8 daemon prio=10 os_prio=0 tid=0x0000000031e3a800 nid=0x4cc8 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"JDWP Event Helper Thread" #7 daemon prio=10 os_prio=0 tid=0x0000000031e39000 nid=0x2144 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"JDWP Transport Listener: dt_socket" #6 daemon prio=10 os_prio=0 tid=0x0000000031e2d000 nid=0x4910 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000031e24800 nid=0x5894 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000031dca800 nid=0x5124 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x0000000031da6800 nid=0x4038 in Object.wait() [0x000000003371e000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000006c1908ec0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:143)
        - locked <0x00000006c1908ec0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:164)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:212)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000003031e800 nid=0x72f4 in Object.wait() [0x000000003361f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000006c1906b68> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000006c1906b68> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x0000000030316000 nid=0x72c0 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x000000000393b000 nid=0x65a4 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000000000393c800 nid=0x7054 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000393e000 nid=0x6b00 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000393f800 nid=0x6514 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000003942800 nid=0x69bc runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000003944000 nid=0x3e34 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000003947000 nid=0x6be4 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000003948000 nid=0x4eb4 runnable
"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000000003949800 nid=0x6a10 runnable
"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x000000000394a800 nid=0x69a8 runnable
"GC task thread#10 (ParallelGC)" os_prio=0 tid=0x000000000394b800 nid=0x6120 runnable
"GC task thread#11 (ParallelGC)" os_prio=0 tid=0x000000000394f000 nid=0x5f3c runnable
"GC task thread#12 (ParallelGC)" os_prio=0 tid=0x0000000003950000 nid=0x6518 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x0000000031f68000 nid=0x65f4 waiting on condition
JNI global references: 2358
Found one Java-level deadlock:
=============================
"Thread-1":
  waiting to lock monitor 0x0000000030323d98 (object 0x00000006c1b226d0, a java.lang.Object),
  which is held by "Thread-0"
"Thread-0":
  waiting to lock monitor 0x00000000303266d8 (object 0x00000006c1b226e0, a java.lang.Object),
  which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
        at com.bilibili.juc.job.Test.b(Test.java:26)
        - waiting to lock <0x00000006c1b226d0> (a java.lang.Object)
        - locked <0x00000006c1b226e0> (a java.lang.Object)
        at com.bilibili.juc.job.Test.lambda$main$1(Test.java:43)
        at com.bilibili.juc.job.Test$$Lambda$2/88558700.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"Thread-0":
        at com.bilibili.juc.job.Test.a(Test.java:17)
        - waiting to lock <0x00000006c1b226e0> (a java.lang.Object)
        - locked <0x00000006c1b226d0> (a java.lang.Object)
        at com.bilibili.juc.job.Test.lambda$main$0(Test.java:35)
        at com.bilibili.juc.job.Test$$Lambda$1/128526626.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.

jconsole排查死锁

偏向锁、轻量锁、轻量锁

偏向锁

如果不存在线程竞争的一个线程获得了锁,那么锁就进入偏向状态,此时Mark  Word的结构变为偏向锁结构,锁对象的锁标志位(lock)被改为01,偏向标志位(biased_lock)被改为1,然后线程的ID记录在锁对象的Mark Word中(使用CAS操作完成)。以后该线程获取锁时判断一下线程ID和标志位,就可以直接进入同步块,连CAS操作都不需要,这样就省去了大量有关锁申请的操作,从而也就提升了程序的性能。轻量锁

线程在执行同步块之前, JVM 会先在当前线程的栈桢中创建用于存储锁记录 的空间 ,并将对象头中的 Mark Word 复制到锁记录中,官方称为Displaced Mark Word 。然后线程尝试使用CAS将对 象 头 中的 Mark Word 替换为指向锁记录的指针 。如果成功,当前线程获得锁 ,如果失败,表示其他线程 竞争锁 ,当前线程便尝试使用自旋来获取锁 。

重量锁

当多个线程竞争同一个锁时,会导致除锁的拥有者外,其余线程都会自旋,这将导致自旋次数过多,cpu效率下降,所以会将锁升级为重量级锁。

当一个线程获取了该锁后,其余线程想要获取锁,必须等到这个线程释放锁后才可能获取到,没有获取到锁的线程,就进入了阻塞状态。

邮戳锁

StampedLock是JUC并发包里面JDK1.8版本新增的一个锁,该锁提供了三种模式的读写控制,当调用获取锁的系列函数的时候,会返回一个long 型的变量,该变量被称为戳记(stamp),这个戳记代表了锁的状态。


try系列获取锁的函数,当获取锁失败后会返回为0的stamp值。当调用释放锁和转换锁的方法时候需要传入获取锁时候返回的stamp值。

相关文章
|
26天前
|
安全 Java 编译器
线程安全问题和锁
本文详细介绍了线程的状态及其转换,包括新建、就绪、等待、超时等待、阻塞和终止状态,并通过示例说明了各状态的特点。接着,文章深入探讨了线程安全问题,分析了多线程环境下变量修改引发的数据异常,并通过使用 `synchronized` 关键字和 `volatile` 解决内存可见性问题。最后,文章讲解了锁的概念,包括同步代码块、同步方法以及 `Lock` 接口,并讨论了死锁现象及其产生的原因与解决方案。
56 10
线程安全问题和锁
|
6天前
|
安全 Java 调度
Java编程时多线程操作单核服务器可以不加锁吗?
Java编程时多线程操作单核服务器可以不加锁吗?
21 2
|
21天前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
1月前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
105 6
【Java学习】多线程&JUC万字超详解
|
2月前
|
数据采集 存储 安全
如何确保Python Queue的线程和进程安全性:使用锁的技巧
本文探讨了在Python爬虫技术中使用锁来保障Queue(队列)的线程和进程安全性。通过分析`queue.Queue`及`multiprocessing.Queue`的基本线程与进程安全特性,文章指出在特定场景下使用锁的重要性。文中还提供了一个综合示例,该示例利用亿牛云爬虫代理服务、多线程技术和锁机制,实现了高效且安全的网页数据采集流程。示例涵盖了代理IP、User-Agent和Cookie的设置,以及如何使用BeautifulSoup解析HTML内容并将其保存为文档。通过这种方式,不仅提高了数据采集效率,还有效避免了并发环境下的数据竞争问题。
如何确保Python Queue的线程和进程安全性:使用锁的技巧
|
5天前
|
数据采集 消息中间件 并行计算
进程、线程与协程:并发执行的三种重要概念与应用
进程、线程与协程:并发执行的三种重要概念与应用
16 0
|
10天前
|
存储 算法 Java
关于python3的一些理解(装饰器、垃圾回收、进程线程协程、全局解释器锁等)
该文章深入探讨了Python3中的多个重要概念,包括装饰器的工作原理、垃圾回收机制、进程与线程的区别及全局解释器锁(GIL)的影响等,并提供了详细的解释与示例代码。
15 0
|
2月前
|
Java 开发者
Java多线程教程:使用ReentrantLock实现高级锁功能
Java多线程教程:使用ReentrantLock实现高级锁功能
34 1
|
1月前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
18 0
|
2月前
|
数据采集 Java Python
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器