返回顶部
首页 > 资讯 > 精选 >如何在Java中使用同步容器
  • 914
分享到

如何在Java中使用同步容器

2023-06-15 00:06:22 914人浏览 薄情痞子
摘要

今天就跟大家聊聊有关如何在Java中使用同步容器,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3

今天就跟大家聊聊有关如何在Java中使用同步容器,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。

Java可以用来干什么

Java主要应用于:1. web开发;2. Android开发;3. 客户端开发;4. 网页开发;5. 企业级应用开发;6. Java大数据开发;7.游戏开发等。

一、什么是同步容器

定义:就是把容器类同步化,这样我们在并发中使用容器时,就不用手动同步,因为内部已经自动同步了

例子:比如Vector就是一个同步容器类,它的同步化就是把内部的所有方法都上(有的重载方法没上锁,但是最终调用的方法还是有锁的)

源码:Vector.add

// 通过synchronized为add方法上锁public synchronized boolean add(E e) {  modCount++;  ensureCapacityHelper(elementCount + 1);  elementData[elementCount++] = e;  return true;}

同步容器主要分两类:

普通类:Vector、Stack、HashTable

内部类:Collections创建的内部类,比如Collections.SynchronizedList、 Collections.SynchronizedSet等

那这两种有没有区别呢?

当然是有的,刚开始的时候(Java1.0)只有第一种同步容器(Vector等)

但是因为Vector这种类太局气了,它就想着把所有的东西都弄过来自己搞(Vector通过toArray转为己有,HashTable通过putAll转为己有);

源码:Vector构造函数

public Vector(Collection<? extends E> c) {// 这里通过toArray将传来的集合 转为己有  elementData = c.toArray();  elementCount = elementData.length;  // c.toArray might (incorrectly) not return Object[] (see 6260652)  if (elementData.getClass() != Object[].class)    elementData = Arrays.copyOf(elementData, elementCount, Object[].class);}

所以就有了第二种同步容器类(通过工厂方法创建的内部容器类),它就比较聪明了,它只是把原有的容器进行包装(通过this.list = list直接指向需要同步的容器),然后局部加锁,这样一来,即生成了线程安全的类,又不用太费力;

源码:Collections.SynchronizedList构造函数

SynchronizedList(List<E> list) {  super(list);  // 这里只是指向传来的list,不转为己有,后面的相关操作还是基于原有的list集合  this.list = list;}

他们之间的区别如下:

两种同步容器的区别普通类内部类
锁的对象不可指定,只能this可指定,默认this
锁的范围方法体(包括迭代)代码块(不包括迭代)
适用范围窄-个别容器广-所有容器

这里我们重点说下锁的对象:

  • 普通类锁的是当前对象this(锁在方法上,默认this对象);

  • 内部类锁的是mutex属性,这个属性默认是this,但是可以通过构造函数(或工厂方法)来指定锁的对象

源码:Collections.SynchronizedCollection构造函数

final Collection<E> c;  // Backing Collection// 这个就是锁的对象final Object mutex;     // Object on which to synchronizeSynchronizedCollection(Collection<E> c) {  this.c = Objects.requireNonNull(c);// 初始化为 this  mutex = this;}SynchronizedCollection(Collection<E> c, Object mutex) {  this.c = Objects.requireNonNull(c);  this.mutex = Objects.requireNonNull(mutex);}

这里要注意一点就是,内部类的迭代器没有同步(Vector的迭代器有同步),需要手动加锁来同步

源码:Vector.Itr.next 迭代方法(有上锁)

public E next() {  synchronized (Vector.this) {    checkForComodification();    int i = cursor;    if (i >= elementCount)      throw new NoSuchElementException();    cursor = i + 1;    return elementData(lastRet = i);  }}

源码:Collections.SynchronizedCollection.iterator 迭代器(没上锁)

public Iterator<E> iterator() {  // 这里会直接实现类的迭代器(比如ArrayList,它里面的迭代器肯定是没上锁的)  return c.iterator(); // Must be manually synched by user!}

二、为什么要有同步容器

因为普通的容器类(比如ArrayList)是线程不安全的,如果是在并发中使用,我们就需要手动对其加锁才会安全,这样的话就很麻烦;

所以就有了同步容器,它来帮我们自动加锁

下面我们用代码来对比下

线程不安全的类:ArrayList

public class SyncCollectionDemo {        private List<Integer> listNoSync;    public SyncCollectionDemo() {        this.listNoSync = new ArrayList<>();    }    public void addNoSync(int temp){        listNoSync.add(temp);    }    public static void main(String[] args) throws InterruptedException {        SyncCollectionDemo demo = new SyncCollectionDemo();// 创建10个线程        for (int i = 0; i < 10; i++) {// 每个线程执行100次添加操作          new Thread(()->{                for (int j = 0; j < 1000; j++) {                    demo.addNoSync(j);                }            }).start();        }    }}

上面的代码看似没问题,感觉就算有问题也应该是插入的顺序比较乱(多线程交替插入)

但实际上运行会发现,可能会报错数组越界,如下所示:

如何在Java中使用同步容器

原因有二:

因为ArrayList.add操作没有加锁,导致多个线程可以同时执行add操作add操作时,如果发现list的容量不足,会进行扩容,但是由于多个线程同时扩容,就会出现扩容不足的问题

源码:ArrayList.grow扩容

// 扩容方法private void grow(int minCapacity) {        // overflow-conscious code        int oldCapacity = elementData.length;// 这里可以看到,每次扩容增加一半的容量  int newCapacity = oldCapacity + (oldCapacity >> 1);        if (newCapacity - minCapacity < 0)            newCapacity = minCapacity;        if (newCapacity - MAX_ARRAY_SIZE > 0)            newCapacity = hugeCapacity(minCapacity);        // minCapacity is usually close to size, so this is a win:        elementData = Arrays.copyOf(elementData, newCapacity);    }

可以看到,扩容是基于之前的容量进行的,因此如果多个线程同时扩容,那扩容基数就不准确了,结果就会有问题

线程安全的类:Collections.SynchronizedList

public class SyncCollectionDemo {    private List<Integer> listSync;    public SyncCollectionDemo() {      // 这里包装一个空的ArrayList        this.listSync = Collections.synchronizedList(new ArrayList<>());    }    public void addSync(int j){      // 内部是同步操作: synchronized (mutex) {return c.add(e);}        listSync.add(j);    }    public static void main(String[] args) throws InterruptedException {        SyncCollectionDemo demo = new SyncCollectionDemo();        for (int i = 0; i < 10; i++) {            new Thread(()->{                for (int j = 0; j < 100; j++) {                    demo.addSync(j);                }            }).start();        }        TimeUnit.SECONDS.sleep(1);      // 输出1000        System.out.println(demo.listSync.size());    }}

输出正确,因为现在ArrayList被Collections包装成了一个线程安全的类

这就是为啥会有同步容器的原因:因为同步容器使得并发编程时,线程更加安全

三、同步容器的优缺点

一般来说,都是先说优点,再说缺点

但是我们这次先说优点

优点:

  • 并发编程中,独立操作是线程安全的,比如单独的add操作

缺点(是的,优点已经说完了):

  • 性能差,基本上所有方法都上锁,完美的诠释了“宁可错杀一千,不可放过一个”

  • 复合操作,还是不安全,比如putIfAbsent操作(如果没有则添加)

  • 快速失败机制,这种机制会报错提示ConcurrentModificationException,一般出现在当某个线程在遍历容器时,其他线程恰好修改了这个容器的长度

为啥第三点是缺点呢?

因为它只能作为一个建议,告诉我们有并发修改异常,但是不能保证每个并发修改都会爆出这个异常

爆出这个异常的前提如下:

源码:Vector.Itr.checkForComodification 检查容器修改次数

final void checkForComodification() {  // modCount:容器的长度变化次数, expectedModCount:期望的容器的长度变化次数  if (modCount != expectedModCount)    throw new ConcurrentModificationException();}

那什么情况下并发修改不会爆出异常呢?有两种:

遍历没加锁的情况:对于第二种同步容器(Collections内部类)来说,假设线程A修改了modCount的值,但是没有同步到线程B,那么线程B遍历就不会发生异常(但实际上问题已经存在了,只是暂时没有出现)

依赖线程执行顺序的情况:对于所有的同步容器来说,假设线程B已经遍历完了容器,此时线程A才开始遍历修改,那么也不会发生异常

代码就不贴了,大家感兴趣的可以直接写几个线程遍历试试,多运行几次,应该就可以看到效果(不过第一种情况也是基于理论分析,实际代码我这边也没跑出来)

根据阿里巴巴的开发规范:不要在 foreach 循环里进行元素的 remove/add 操作。remove 元素请使用 Iterator方式,如果并发操作,需要对 Iterator 对象加锁。

这里解释下,关于List.remove和Iterator.remove的区别

  • Iterator.remove:会同步修改expectedModCount=modCount

  • list.remove:只会修改modCount,因为expectedModCount属于iterator对象的属性,不属于list的属性(但是也可以间接访问)

源码:ArrayList.remove移除元素操作

public E remove(int index) {        rangeCheck(index);// 1. 这里修改了 modCount        modCount++;        E oldValue = elementData(index);        int numMoved = size - index - 1;        if (numMoved > 0)            System.arraycopy(elementData, index+1, elementData, index,                             numMoved);        elementData[--size] = null; // clear to let GC do its work        return oldValue;    }

源码:ArrayList.Itr.remove迭代器移除元素操作

public void remove() {            if (lastRet < 0)                throw new IllegalStateException();            checkForComodification();            try {              // 1. 这里调用上面介绍的list.romove,修改modCount                ArrayList.this.remove(lastRet);                cursor = lastRet;                lastRet = -1;              // 2. 这里再同步更新 expectedModCount                expectedModCount = modCount;            } catch (IndexOutOfBoundsException ex) {                throw new ConcurrentModificationException();            }        }

由于同步容器的这些缺点,于是就有了并发容器(下期来介绍)

四、同步容器的使用场景

多用在并发编程,但是并发量又不是很大的场景,比如一些简单的个人博客系统(具体多少并发量算大,这个也是分很多情况而论的,并不是说每秒处理超过多少个请求,就说是高并发,还要结合吞吐量、系统响应时间等多个因素一起考虑)

具体点来说的话,有以下几个场景:

  • 写多读少,这个时候同步容器和并发容器的性能差别不大(并发容器可以并发读)

  • 自定义的复合操作,比如getLast等操作(putIfAbsent就算了,因为并发容器有默认提供这个复合操作)

  • 等等

总结

什么是同步容器:就是把容器类同步化,这样我们在并发中使用容器时,就不用手动同步,因为内部已经自动同步了

为什么要有同步容器:因为普通的容器类(比如ArrayList)是线程不安全的,如果是在并发中使用,我们就需要手动对其加锁才会安全,这样的话就很太麻烦;所以就有了同步容器,它来帮我们自动加锁

同步容器的优缺点:

优点独立操作,线程安全




缺点 复合操作,还是不安全,性能差快速失败机制,只适合bug调试

同步容器的使用场景

多用在并发量不是很大的场景,比如个人博客、后台系统等

具体点来说,有以下几个场景:

  • 写多读少:这个时候同步容器和并发容器差别不是很大

  • 自定义复合操作:比如getLast等复合操作,因为同步容器都是单个操作进行上锁的,所以可以很方便地去拼接复合操作(记得外部加锁)

看完上述内容,你们对如何在Java中使用同步容器有进一步的了解吗?如果还想了解更多知识或者相关内容,请关注编程网精选频道,感谢大家的支持。

--结束END--

本文标题: 如何在Java中使用同步容器

本文链接: https://lsjlt.com/news/275899.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 如何在Java中使用同步容器
    今天就跟大家聊聊有关如何在Java中使用同步容器,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3...
    99+
    2023-06-15
  • 如何在Laravel中使用Java容器进行同步?
    Laravel是一款非常流行的PHP框架,它的使用已经非常广泛。Java容器是一种非常强大的工具,可以帮助我们完成各种各样的任务。在本篇文章中,我们将会介绍如何在Laravel中使用Java容器进行同步。 简介 Java容器是一种非常流...
    99+
    2023-09-14
    容器 同步 laravel
  • SynchronousQueue同步器如何在java项目中使用
    SynchronousQueue同步器如何在java项目中使用 ?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。SynchronousQueue   ...
    99+
    2023-05-31
    java synchronousqueue ava
  • 如何在容器中使用Go和Shell进行同步?
    在当今的软件开发中,容器技术已成为一个非常重要的概念。容器允许开发人员将应用程序打包在一个独立、可移植的环境中,这使得应用程序的部署和管理变得更加简单和高效。在这篇文章中,我们将探讨如何在容器中使用Go和Shell进行同步。 一、什么是容器...
    99+
    2023-09-22
    shell 同步 容器
  • 如何在Java中使用AbstractQueuedSynchronizer同步框架
    这篇文章将为大家详细讲解有关如何在Java中使用AbstractQueuedSynchronizer同步框架,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。AbstractQueuedSync...
    99+
    2023-05-31
    java abstractqueuedsynchronizer
  • 如何正确的在java中使用同步锁
    这篇文章给大家介绍如何正确的在java中使用同步锁,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。同步锁分类对象锁(this)类锁(类的字节码文件对象即类名.class)字符串锁(比较特别)应用场景在多线程下对共享资源的...
    99+
    2023-05-31
    java 同步锁 ava
  • 如何在 Java 中使用 Git 同步编程算法?
    Git 是一款流行的版本控制工具,它可以帮助我们管理代码,并且可以方便地协作开发。在 Java 编程中,使用 Git 进行代码同步非常常见。本文将介绍如何在 Java 中使用 Git 同步编程算法,以及如何编写适合 Git 版本控制的代码...
    99+
    2023-09-25
    git 同步 编程算法
  • 如何在java中实现同步
    如何在java中实现同步?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3....
    99+
    2023-06-14
  • Laravel与Java容器:如何实现同步?
    Laravel和Java都是非常流行的Web开发框架,但它们的语言和环境却不同。如何在这两个框架之间实现同步?这是本文将讨论的问题。 首先,让我们来看一下Laravel和Java的区别。Laravel是一个基于PHP的Web框架,它采用了...
    99+
    2023-09-14
    容器 同步 laravel
  • Java容器同步技术在Laravel中的应用实践
    在现代Web应用程序中,同步和异步处理都是非常常见的。而也成为了一个热门话题。本文将介绍Java容器同步技术的概念、原理和在Laravel中的应用实践。 一、Java容器同步技术的概念 Java容器同步技术是指Java容器在多线程环境下保...
    99+
    2023-09-14
    容器 同步 laravel
  • 如何在ASP环境中实现Unix容器同步?
    在ASP环境中实现Unix容器同步是一个非常有挑战性的任务。本文将介绍如何使用ASP和Unix容器实现同步,并提供一些示例代码,以帮助您开始实现您自己的同步应用程序。 首先,我们需要了解Unix容器是什么。Unix容器是一种操作系统级虚拟化...
    99+
    2023-06-18
    同步 unix 容器
  • 如何在 Go 中使用WaitGroup同步 Goroutine?
    如何在 Go 中使用 WaitGroup 同步 Goroutine? 什么是 WaitGroup? WaitGroup 是 Go 中的一个内置类型,用于协调并发操作,它可以用来确保一组...
    99+
    2024-05-15
    go
  • Java线程同步如何在不同线程中调用
    本篇文章为大家展示了Java线程同步如何在不同线程中调用,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。Java线程同步需要我们大家不断的学习,但是在学习的时候有些重要的代码还是需要注意,下面我们就来...
    99+
    2023-06-17
  • 如何在Java IDE中使用容器索引?
    Java是一门非常流行的编程语言,它的生态系统中有许多优秀的开发工具和框架。在Java开发中,容器是不可或缺的一部分,它们可以帮助我们快速构建应用程序并管理它们的生命周期。本文将介绍如何在Java IDE中使用容器索引,以便更轻松地管理和使...
    99+
    2023-09-23
    容器 索引 ide
  • 如何在Java IDE中使用Shell和容器?
    Java是一种广泛使用的编程语言,而Shell和容器则是与Java密切相关的两个重要概念。本文将介绍如何在Java IDE中使用Shell和容器,以及如何通过演示代码来实现这一过程。 一、什么是Shell? Shell是一种命令行解释器,它...
    99+
    2023-07-18
    ide shell 容器
  • 如何在Java中使用同步函数生成二维码?
    在Java中生成二维码是一项非常常见的任务。如果您需要在多个线程之间共享数据,那么同步代码是非常重要的。在本文中,我们将介绍如何在Java中使用同步函数生成二维码。 一、什么是二维码? 二维码是一种矩阵条形码,它可以存储大量信息,包括URL...
    99+
    2023-10-17
    函数 同步 二维码
  • 如何在 Laravel 中使用 PHP 同步数组?
    Laravel 是一个流行的 PHP 框架,它提供了许多方便的工具和功能,使开发人员能够更轻松地构建高质量的 Web 应用程序。在 Laravel 中,使用 PHP 同步数组是非常常见的操作,下面我们来详细了解一下如何在 Laravel 中...
    99+
    2023-09-18
    同步 laravel 数组
  • 如何在 Unix 系统中使用 Java 进行打包和同步?
    在 Unix 系统中使用 Java 进行打包和同步可以帮助开发人员有效地管理和部署项目。本文将介绍如何使用 Java 进行打包和同步,以及如何使用代码演示来说明这些过程。 一、打包 打包是将一个项目的所有文件打包成一个单独的文件或文件夹的过...
    99+
    2023-06-20
    打包 同步 unix
  • Java同步框架API:如何在项目中正确使用它?
    在Java项目开发中,多线程编程是非常常见的需求。但是,在多线程并发环境下,线程安全问题是一个十分容易被忽视的问题。如果没有正确地处理线程安全问题,很容易导致数据的不一致性、程序的崩溃等问题。因此,Java提供了一些同步框架API,用于帮...
    99+
    2023-09-05
    同步 框架 api
  • 一步步教您如何在Java IDE中设置npm容器
    在Java开发中,使用npm来管理JavaScript包和依赖是非常常见的。然而,在使用npm之前,需要先设置一个npm容器,本文将,方便您进行JavaScript开发。 首先,我们需要安装Node.js,因为npm是Node.js的包管理...
    99+
    2023-09-17
    ide npm 容器
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作