多线程 - 错误的加锁场景

易错点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 对象。

posted on   frank_cui  阅读(371)  评论(0编辑  收藏  举报

编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5

导航

统计

levels of contents
点击右上角即可分享
微信分享提示