0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

synchronized 的几种错误用法

科技绿洲 来源:Java技术指北 作者:Java技术指北 2023-10-09 10:25 次阅读

synchronized 在我们平常工作中也是挺常用的, 对于摆脱多线程问题很有帮助。但是如果synchronized被错误使用时,可能会给我们带来很多麻烦。

在本文中,我们将讨论与同步相关的一些不好的做法,以及针对每个使用情况的更好的方法。

同步的原则

一般来说,我们应该只对那些我们确信没有外部代码会锁定的对象进行同步。

换句话说,使用池化或可重复使用的对象进行同步是一种不好的做法。原因是池化/可重用对象可以被JVM中的其他进程访问,外部/不被信任的代码对这些对象的任何修改都会导致死锁和非确定性行为。

现在,让我们来讨论基于某些类型的同步原则,如String、Boolean、Integer和Object。

String 字面量

1.错误用法

字符串字面量是有池的,在Java中经常被重复使用。因此,不建议使用String类型与 synchronized关键字进行同步。

public void stringBadPractice1() {
    String stringLock = "LOCK_STRING";
    synchronized (stringLock) {
        // ...
    }
}

同样地,如果我们使用private final String字面,它仍然是从常量池中引用的。

private final String stringLock = "LOCK_STRING";
public void stringBadPractice2() {
    synchronized (stringLock) {
        // ...
    }
}

此外,为了同步,内接字符串被认为是不好的做法。

private final String internedStringLock = new String("LOCK_STRING").intern();
public void stringBadPractice3() {
  synchronized (internedStringLock) {
      // ...
  }
}

根据Javadocs,intern方法为我们获得了String对象的规范表示。换句话说,intern方法从池中返回一个String--如果它不在池中,则明确地将它添加到池中--它的内容与这个String相同。

因此,在可重用对象上的同步问题对于内部的String对象也是存在的。

注意:所有的String字面符号和以字符串为值的常量表达式都是自动实现的。

2.正确用法

为了避免在String字面上进行同步的不良做法,建议使用new关键字创建一个新的String实例。

让我们在已经讨论过的代码中解决这个问题。首先,我们将创建一个新的String对象,以拥有一个唯一的引用(避免任何重复使用)和它自己的内在锁,这有助于同步。

然后,我们保持该对象的private和final,以防止任何外部/不受信任的代码访问它。

private final String stringLock = new String("LOCK_STRING");
public void stringSolution() {
    synchronized (stringLock) {
        // ...
    }
}

Boolean 字面量

Boolean类型有两个值,即true和false,不适合用于锁定目的。与JVM中的String字面量类似,boolean字面量也共享Boolean类的唯一实例。

让我们来看看一个在Boolean锁对象上同步的错误用法例子。

private final Boolean booleanLock = Boolean.FALSE;
public void booleanBadPractice() {
    synchronized (booleanLock) {
        // ...
    }
}

在这里,如果任何外部代码也在具有相同值的Boolean字面上进行同步,系统就会变得没有反应,或者导致死锁的情况。

因此,我们不建议使用Boolean对象作为同步锁。

原始类型的包装类

1. 错误用法

与boolean字段类似,原始类型的包装类可能会重复使用某些值的实例。原因是JVM会缓存和共享可以表示为字节的值。

例如,让我们写一个在 Integer 上进行同步的错误用法例子。

private int count = 0;
private final Integer intLock = count; 
public void boxedPrimitiveBadPractice() { 
    synchronized (intLock) {
        count++;
        // ... 
    } 
}

2.正确用法

然而,与boolean字面量不同,在原始类型的包装类上同步的解决方案是创建一个新实例。

与String对象类似,我们应该使用new关键字来创建一个唯一的Integer对象的实例,该实例有自己的内在锁,并保持其private和final。

private int count = 0;
private final Integer intLock = new Integer(count);
public void boxedPrimitiveSolution() {
    synchronized (intLock) {
        count++;
        // ...
    }
}

类同步

当一个类用this关键字实现方法同步或块同步时,JVM使用对象本身作为监视器(其固有锁)。

不受信任的代码可以获得并无限期地持有一个可访问类的内在锁。因此,这可能会导致死锁的情况。

1.错误用法

例如,让我们创建Animal类,它有一个synchronized方法setName和一个带有synchronized块的方法setOwner。

public class Animal {
    private String name;
    private String owner;
    
    // getters and constructors
    
    public synchronized void setName(String name) {
        this.name = name;
    }

    public void setOwner(String owner) {
        synchronized (this) {
            this.owner = owner;
        }
    }
}

现在,让我们写一些错误用法,创建一个Animal类的实例,并对其进行同步。

Animal animalObj = new Animal("Tommy", "John");
synchronized (animalObj) {
    while(true) {
        Thread.sleep(Integer.MAX_VALUE);
    }
}

在这里,不受信任的代码例子引入了一个无限期的延迟,阻止了setName和setOwner方法的实现获得同一个锁。

2.正确用法

防止这个漏洞的解决方案是私人锁对象。

我们的想法是使用与我们类中定义的Object类的private final实例相关的内在锁来代替对象本身的内在锁。

另外,我们应该使用块同步来代替方法同步,以增加灵活性,使非同步的代码不在块中。

所以,让我们对我们的Animal类进行必要的修改。

public class Animal {
    // ...

    private final Object objLock1 = new Object();
    private final Object objLock2 = new Object();

    public void setName(String name) {
        synchronized (objLock1) {
            this.name = name;
        }
    }

    public void setOwner(String owner) {
        synchronized (objLock2) {
            this.owner = owner;
        }
    }
}

在这里,为了提高并发性,我们通过定义多个private final锁对象来细化锁定方案,以分离我们对两个方法--setName和setOwner的同步关注。

此外,如果实现同步块的方法修改了一个静态变量,我们必须通过锁定静态对象来实现同步。

private static int staticCount = 0;
private static final Object staticObjLock = new Object();
public void staticVariableSolution() {
    synchronized (staticObjLock) {
        count++;
        // ...
    }
}

总结

在这篇文章中,我们讨论了一些与某些类型的同步有关的坏做法,如String、Boolean、Integer和Object。

本文最重要的启示是,不建议使用池化或可重复使用的对象进行同步。

另外,建议在Object类的private final实例上进行同步。这样的对象将无法被外部/不被信任的代码访问,否则这些代码可能会与我们的公共类交互,从而减少这种交互导致死锁的可能性。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 多线程
    +关注

    关注

    0

    文章

    278

    浏览量

    19943
  • 代码
    +关注

    关注

    30

    文章

    4779

    浏览量

    68519
  • string
    +关注

    关注

    0

    文章

    40

    浏览量

    4732
收藏 人收藏

    评论

    相关推荐

    Python学习:“-m”选项典型用法和原理解析

    看了前面的几种典型用法,你是否开始好奇:“-m”是怎么运作的?它是怎么实现的?
    发表于 11-20 15:09 2980次阅读
    Python学习:“-m”选项典型<b class='flag-5'>用法</b>和原理解析

    C语言指针和数组的错误用法

    上线后出现一个异常。我才觉得我对指针只是学废了。找了一些指针和数组的博客资料,记录一下。希望下次不会再犯类似的错误
    发表于 09-28 09:12 694次阅读

    掌握find命令的这几种用法,就没有找不到的文件!

    今天浩道跟大家分享linux下关于find命令的几种经典用法,让你在linux茫茫的文件海洋中可以找出任何想要的文件!
    发表于 12-06 14:19 810次阅读

    常见的几种日期对象用法

    前的 util.Date 以及 Calander 使用起来更加的方便直观,下面介绍几种常见的日期对象用法。 LocalDateTime:日期加时间的日期对象,包含年月日时分秒 LocalDate:日期类,包含年月日
    的头像 发表于 09-25 11:10 741次阅读
    常见的<b class='flag-5'>几种</b>日期对象<b class='flag-5'>用法</b>

    while的使用形式有哪几种?分别有什么用法

    请问下while的使用形式有哪几种?分别有什么用法
    发表于 07-15 12:29

    C语言中的数据类型有哪几种?const有哪些用法

    C语言中的数据类型有哪几种?const有哪些用法?作用域与static用法是什么?extern是如何去使用的?
    发表于 07-22 06:51

    Synchronized multi-spark modul

    Synchronized multi-spark module (SMSM) for Electronic Ignition Devices (EID)
    发表于 12-29 09:09 840次阅读
    <b class='flag-5'>Synchronized</b> multi-spark modul

    关于缓存的四大误用,你中招了吗?

    缓存,是互联网分层架构中,非常重要的一个部分,通常用它来降低数据库压力,提升系统整体性能,缩短访问时间。 有架构师说“缓存是万金油,哪里有问题,加个缓存,就能优化”,缓存的滥用,可能会导致一些错误用法
    发表于 07-01 10:00 3172次阅读
    关于缓存的四大<b class='flag-5'>误用</b>,你中招了吗?

    Java并发编程中线程同步的常用手段synchronized用法

    变量的修改能够及时可见,获得锁的线程操作完毕后会将所数据刷新到共享内存区[1] 有序性:不解决重排序,但保证有序性 synchronized用法有三个: 修饰实例方法 修饰静态方法 修饰代码块 1. 修饰实例方法 synchronize
    的头像 发表于 04-04 11:30 1167次阅读
    Java并发编程中线程同步的常用手段<b class='flag-5'>synchronized</b><b class='flag-5'>用法</b>

    讲“伏秒平衡”,验证磁性元件的错误用法资料下载

    电子发烧友网为你提供讲“伏秒平衡”,验证磁性元件的错误用法资料下载的电子资料下载,更有其他相关的电路图、源代码、课件教程、中文资料、英文资料、参考设计、用户指南、解决方案等资料,希望可以帮助到广大的电子工程师们。
    发表于 04-03 08:47 15次下载
    讲“伏秒平衡”,验证磁性元件的<b class='flag-5'>错误用法</b>资料下载

    详细介绍synchronized和Object的关键方法和虚拟机实现原理

    编程过程中经常会遇到线程的同步问题,Java 中对同步问题的解决方案比较多(synchronized、JUC、原子操作、volatile、条件变量等),其中synchronized 最方便、简单易用,也是java 编程中使用最多的临界区保护方案。
    的头像 发表于 03-13 10:06 1260次阅读

    synchronized知识合集1

    * 线程安全 * 什么是synchronized关键字? * synchronized实现方式 * 1.修饰实例方法 * 2.修饰静态方法 * 3.修饰代码块
    的头像 发表于 05-11 11:07 447次阅读
    <b class='flag-5'>synchronized</b>知识合集1

    synchronized知识合集2

    * 线程安全 * 什么是synchronized关键字? * synchronized实现方式 * 1.修饰实例方法 * 2.修饰静态方法 * 3.修饰代码块
    的头像 发表于 05-11 11:08 398次阅读

    synchronized的原理与四种用法介绍

    JDK提供的锁分两种,一种是JVM实现的synchronized,是java的关键字,因此在这个关键字作用对象的范围内都是可以保证原子性的,主要是依赖特殊的CPU指令。另一种是JDK提供的代码层面的锁Lock。
    的头像 发表于 06-09 16:13 1088次阅读
    <b class='flag-5'>synchronized</b>的原理与四种<b class='flag-5'>用法</b>介绍

    synchronized的锁膨胀

    初识 synchronized 可以加在方法和类上面,作用于类和对象。下面代码中列出了 synchronized用法。 public class SynchronizedTest
    的头像 发表于 10-10 16:58 471次阅读
    <b class='flag-5'>synchronized</b>的锁膨胀