【工作随手记】并发之synchronized
Published on 2023-01-10 16:20 in 暂未分类 with 是奉壹呀

【工作随手记】并发之synchronized

    synchronized对于java同学肯定都是耳熟能详的必修课了。但是不管对于新手还是老手都有一些容易搞错的点。这里权做一点记录。

    锁的是代码还是对象?

    同步块一般有两种写法。
    1是直接加以方法体上。

    public synchronized void incr3(){
                i++;
        }
    

    2是块小锁粒度的加到代码块

    public void incr() throws InterruptedException { 
            synchronized (SyncIncrDemo.class){
                i++;
            }
        }
    

    不管怎样,锁的都是对象。对于这段代码来说,谁拿到这个锁对象,就能优先执行锁定的代码,且对其它线程互斥。
    代码2显示锁定的类对象,那么代码1在方法体上锁对象是谁呢?

    debug下打印堆栈信息可以很明显的看到代码块1里面的锁是类的实例对象 locked <0x218> (a com.alpha.data.util.SyncIncrDemo)

    Full thread dump

    "main@1" prio=5 tid=0x1 nid=NA waiting
    java.lang.Thread.State: WAITING
    at java.lang.Object.wait(Object.java:-1)
    at java.lang.Thread.join(Thread.java:1252)
    at java.lang.Thread.join(Thread.java:1326)
    at com.alpha.data.util.SyncIncrDemo.main(SyncIncrDemo.java:59)

    "Thread-1@530" prio=5 tid=0xf nid=NA sleeping
    java.lang.Thread.State: TIMED_WAITING
    at java.lang.Thread.sleep(Thread.java:-1)
    at com.alpha.data.util.SyncIncrDemo.incr2(SyncIncrDemo.java:33)
    - locked <0x217> (a com.alpha.data.util.SyncIncrDemo)
    at com.alpha.data.util.SyncIncrDemo.run(SyncIncrDemo.java:49)
    at java.lang.Thread.run(Thread.java:748)

    "Thread-0@529" prio=5 tid=0xe nid=NA sleeping
    java.lang.Thread.State: TIMED_WAITING
    at java.lang.Thread.sleep(Thread.java:-1)
    at com.alpha.data.util.SyncIncrDemo.incr2(SyncIncrDemo.java:33)
    - locked <0x218> (a com.alpha.data.util.SyncIncrDemo)
    at com.alpha.data.util.SyncIncrDemo.run(SyncIncrDemo.java:49)
    at java.lang.Thread.run(Thread.java:748)

    "Finalizer@533" daemon prio=8 tid=0x3 nid=NA waiting
    java.lang.Thread.State: WAITING
    at java.lang.Object.wait(Object.java:-1)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

    "Reference Handler@534" daemon prio=10 tid=0x2 nid=NA waiting
    java.lang.Thread.State: WAITING
    at java.lang.Object.wait(Object.java:-1)
    at java.lang.Object.wait(Object.java:502)
    at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

    "Attach Listener@531" daemon prio=5 tid=0x5 nid=NA runnable
    java.lang.Thread.State: RUNNABLE

    "Signal Dispatcher@532" daemon prio=9 tid=0x4 nid=NA runnable
    java.lang.Thread.State: RUNNABLE

    对于静态方法呢?

    public static synchronized void incr3(){
                i++;
        }
    

    打印堆栈,可以看到是锁的是类对象 locked <0x20f> (a java.lang.Class)
    这也符合预期,因为静态类方法属于类对象,而不是实例对象。

    Full thread dump

    "Thread-0@529" prio=5 tid=0xe nid=NA waiting for monitor entry
    java.lang.Thread.State: BLOCKED
    waiting for Thread-1@530 to release lock on <0x20f> (a java.lang.Class)
    at com.alpha.data.util.SyncIncrDemo.incr(SyncIncrDemo.java:24)
    at com.alpha.data.util.SyncIncrDemo.run(SyncIncrDemo.java:51)
    at java.lang.Thread.run(Thread.java:748)

    "main@1" prio=5 tid=0x1 nid=NA waiting
    java.lang.Thread.State: WAITING
    at java.lang.Object.wait(Object.java:-1)
    at java.lang.Thread.join(Thread.java:1252)
    at java.lang.Thread.join(Thread.java:1326)
    at com.alpha.data.util.SyncIncrDemo.main(SyncIncrDemo.java:61)

    "Thread-1@530" prio=5 tid=0xf nid=NA sleeping
    java.lang.Thread.State: TIMED_WAITING
    blocks Thread-0@529
    at java.lang.Thread.sleep(Thread.java:-1)
    at com.alpha.data.util.SyncIncrDemo.incr(SyncIncrDemo.java:24)
    - locked <0x20f> (a java.lang.Class)
    at com.alpha.data.util.SyncIncrDemo.run(SyncIncrDemo.java:51)
    at java.lang.Thread.run(Thread.java:748)

    "Finalizer@533" daemon prio=8 tid=0x3 nid=NA waiting
    java.lang.Thread.State: WAITING
    at java.lang.Object.wait(Object.java:-1)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

    "Reference Handler@534" daemon prio=10 tid=0x2 nid=NA waiting
    java.lang.Thread.State: WAITING
    at java.lang.Object.wait(Object.java:-1)
    at java.lang.Object.wait(Object.java:502)
    at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

    "Attach Listener@531" daemon prio=5 tid=0x5 nid=NA runnable
    java.lang.Thread.State: RUNNABLE

    "Signal Dispatcher@532" daemon prio=9 tid=0x4 nid=NA runnable
    java.lang.Thread.State: RUNNABLE

    所以,对于synchronized代码块,锁对象为显示的锁定对象;synchronized方法分为静态方法和非静态方法,对于静态方法,锁对象为该class对,否则属于该class的实例对象。

    public static synchronized void incr3(){
                i++;
        }
    等同于
    
        public synchronized void incr3() throws InterruptedException {
            synchronized (SyncIncrDemo.class){
                Thread.sleep(100000);
            }
            i++;
        }
    
    public synchronized void incr3(){
                i++;
        }
    等同于
    
        public synchronized void incr3() throws InterruptedException {
            synchronized (this){
                Thread.sleep(100000);
            }
            i++;
        }
    

    对于相同的竞态场景,必须持相同的锁对象。通俗的讲,一个门只能有一把锁。

    为什么会有这样的问题。这个问题对于新手来讲有点饶,对于有些人来讲是废话。
    源于看到一篇博文,大概是有一个Integer类型的共享变量,多线程并发对其操作,在同步块里对于操作。伪代码

    Integer count = 0;
    
    public void add(){
        synchronized(count){
              count++;
         }
    }
    
    

    这样子很明显是达不到预期结果的。Integer在超出-128到127范围以后,每一个对象都是一个新的实例对象。换句话说,这里想要达到N多人同时想要通过一个通道,通道门上有多把锁,很多人可以同时拿到锁进而并发地通过。达不到预期目的。

    可能有人会不以为然,对代码块加同步锁肯定不能用Integer这种对象啊,这只能是新手犯的错,用this呢?
    前面说到,使用this锁对象为当前class的实例对象,在spring默认单例的情况下,肯定没有问题,但如果某位同事在某天在某种需求场景下改为了多例呢?
    很明显的,多例情况下,就是同一个门多把锁了。

    所以这里是需要注意的点。
    我觉得最安全最健壮的方法是不要在方法上加同步块,一律使用类对象作为锁。

    synchronized(A.class){          
         }
    

    synchronized就一定安全吗

    从所周知,
    synchronized能保证原子性,可见性,有序性,
    volatile能保证可见性和有序性,防止指令重排。
    那么synchronized是不是就一定能保证线程安全呢?共享变量就可以不用volatile了呢?

    我觉得答案是否定的。哪怕是在synchronized内部,如果有共享变量,必须强制使用volatile,这是一个好的编码习惯。

    synchronized 的有序性是持有相同锁的两个同步块只能串行的进入,即被加锁的内容要按照顺序被多个线程执行,但是其内部的同步代码还是会发生重排序,使块与块之间有序可见。
    volatile的有序性是通过插入内存屏障来保证指令按照顺序执行。不会存在后面的指令跑到前面的指令之前来执行。是保证编译器优化的时候不会让指令乱序。
    synchronized 是不能保证指令重排的。

    posted @   是奉壹呀  阅读(116)  评论(0编辑  收藏  举报
    相关博文:
    阅读排行:
    · 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
    · 单线程的Redis速度为什么快?
    · SQL Server 2025 AI相关能力初探
    · AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
    · 展开说说关于C#中ORM框架的用法!
    点击右上角即可分享
    微信分享提示