多线程 - 错误的加锁场景
易错点1:锁一个可变对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | package cn.enjoyedu.ch1.syn; /** * 类说明:错误的加锁和原因分析 */ public class TestIntegerSyn { public static void main(String[] args) throws InterruptedException { Worker worker= new Worker( 1 ); for ( int i= 0 ;i< 5 ;i++) { //注意:这里传入的是同1个worker,因此Worker类里面的Integer i 和 Object obj,5个线程是共享的 //不要误认为是5个worker,而是5个线程用的是同1个worker对象 new Thread(worker).start(); } } private static class Worker implements Runnable{ private Integer i; //5个线程共享 private Object obj = new Object(); //5个线程共享 public Worker(Integer i) { this .i=i; } @Override public void run() { // 如果锁的是i: // >>>假设线程-0先拿到锁,进入同步代码块。其他4个线程被block在对象i=Integer(1)的锁上。由于i++装箱操作会return一个新的integer对象赋给i,此时被锁的i变为i=Integer(2) // 原本4个线程被block在对象i=Integer(1)的锁上(因为线程-0已经拿到锁),但是此时锁的对象变成了i=Integer(2),因此这4个线程可以重新竞争进入代码块。 // >>>当线程-0 i++后,不等它执行完同步代码块释放掉锁,线程-1就会直接进来操作。此时线程-1 拿的锁是线程-0加一之后的对象。。以此类推,等线程-1加一后,线程-2拿的锁是线程-1加一之后的对象 // >>>又因为5个线程共享i,因此,线程-0 /其他线程 sleep之前和之后输出的i会发生变化。因为在sleep的时候,别的线程进来操作了。 // // 如果锁的是obj: // >>>obj同样是5个线程共享的,但由于没有线程对其操作。因此5个线程始终在竞争同一把锁,可以互斥运行。 synchronized (i) { //obj Thread thread=Thread.currentThread(); System.out.println(thread.getName()+ "-------@" +System.identityHashCode(i)); i++; System.out.println(thread.getName()+ "-------" +i+ "-@" +System.identityHashCode(i)); try { Thread.sleep( 3000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(thread.getName()+ "-------" +i+ "--@" +System.identityHashCode(i)); } } } } |
如果锁的是可变对象,i 。结果:5个线程会无法互斥。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Thread- 0 ------- @129460851 Thread- 0 ------- 2 - @158369013 Thread- 2 ------- @158369013 Thread- 2 ------- 3 - @1898749074 Thread- 3 ------- @1898749074 Thread- 3 ------- 4 - @1763156594 Thread- 4 ------- @1763156594 Thread- 4 ------- 5 - @825455695 Thread- 4 ------- 5 -- @825455695 Thread- 2 ------- 5 -- @825455695 Thread- 0 ------- 5 -- @825455695 Thread- 3 ------- 5 -- @825455695 Thread- 1 ------- @825455695 Thread- 1 ------- 6 - @1779415244 Thread- 1 ------- 6 -- @1779415244 |
修改成不可变对象, obj。结果:5个线程必须互斥执行,一个执行完了再执行下一个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | Thread- 0 ------- @1898749074 Thread- 0 ------- 2 - @2146135239 Thread- 0 ------- 2 -- @2146135239 Thread- 4 ------- @2146135239 Thread- 4 ------- 3 - @299052996 Thread- 4 ------- 3 -- @299052996 Thread- 3 ------- @299052996 Thread- 3 ------- 4 - @976006504 Thread- 3 ------- 4 -- @976006504 Thread- 2 ------- @976006504 Thread- 2 ------- 5 - @658235253 Thread- 2 ------- 5 -- @658235253 Thread- 1 ------- @658235253 Thread- 1 ------- 6 - @825455695 Thread- 1 ------- 6 -- @825455695 |
原因:虽然我们对 i 进行了加锁,但是
但是当我们反编译这个类的 class 文件后,可以看到 i++实际是,
本质上是返回了一个新的 Integer 对象。也就是每个线程实际加锁的是不同 的 Integer 对象。
分类:
Java 多线程
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?