被一个熟悉的面试题问懵了:StringBuilder 为什么线程不安全?

作者 | 千山qianshan 

来源 | juejin.im/post/5d6228046fb9a06add4e37fe

前言

周五去面试又被面试的一个问题问哑巴了

面试官:StringBuilder和StringBuffer的区别在哪? 

我:StringBuilder不是线程安全的,StringBuffer是线程安全的 

面试官:那StringBuilder不安全的点在哪儿? 

我:。。。(哑巴了)

在这之前我只记住了StringBuilder不是线程安全的,StringBuffer是线程安全的这个结论,至于StringBuilder为什么不安全从来没有去想过。

分析

在分析设个问题之前我们要知道StringBuilder和StringBuffer的内部实现跟String类一样,都是通过一个char数组存储字符串的,不同的是String类里面的char数组是final修饰的,是不可变的,而StringBuilder和StringBuffer的char数组是可变的。

首先通过一段代码去看一下多线程操作StringBuilder对象会出现什么问题

public class StringBuilderDemo {public static void main(String[] args) throws InterruptedException {StringBuilder stringBuilder = new StringBuilder();for (int i = 0; i < 10; i++){new Thread(new Runnable() {@Overridepublic void run() {for (int j = 0; j < 1000; j++){stringBuilder.append("a");}}}).start();}Thread.sleep(100);System.out.println(stringBuilder.length());}}

我们能看到这段代码创建了10个线程,每个线程循环1000次往StringBuilder对象里面append字符。正常情况下代码应该输出10000,但是实际运行会输出什么呢?

我们看到输出了“9326”,小于预期的10000,并且还抛出了一个ArrayIndexOutOfBoundsException异常(异常不是必现)。

1、为什么输出值跟预期值不一样

我们先看一下StringBuilder的两个成员变量(这两个成员变量实际上是定义在AbstractStringBuilder里面的,StringBuilder和StringBuffer都继承了AbstractStringBuilder)

//存储字符串的具体内容
char[] value;
//已经使用的字符数组的数量
int count;

再看StringBuilder的append()方法:

@Override
public StringBuilder append(String str) {super.append(str);return this;
}

StringBuilder的append()方法调用的父类AbstractStringBuilder的append()方法

public AbstractStringBuilder append(String str) {if (str == null)return appendNull();int len = str.length();ensureCapacityInternal(count + len);str.getChars(0, len, value, count);count += len;return this;
}

我们先不管代码的第五行和第六行干了什么,直接看第七行,count += len不是一个原子操作。假设这个时候count值为10,len值为1,两个线程同时执行到了第七行,拿到的count值都是10,执行完加法运算后将结果赋值给count,所以两个线程执行完后count值为11,而不是12。这就是为什么测试代码输出的值要比10000小的原因。

2、为什么会抛出ArrayIndexOutOfBoundsException异常。

我们看回AbstractStringBuilder的append()方法源码的第五行,ensureCapacityInternal()方法是检查StringBuilder对象的原char数组的容量能不能盛下新的字符串,如果盛不下就调用expandCapacity()方法对char数组进行扩容。

private void ensureCapacityInternal(int minimumCapacity) {// overflow-conscious codeif (minimumCapacity - value.length > 0)expandCapacity(minimumCapacity);
}

扩容的逻辑就是new一个新的char数组,新的char数组的容量是原来char数组的两倍再加2,再通过System.arryCopy()函数将原数组的内容复制到新数组,最后将指针指向新的char数组。

void expandCapacity(int minimumCapacity) {//计算新的容量int newCapacity = value.length * 2 + 2;//中间省略了一些检查逻辑...value = Arrays.copyOf(value, newCapacity);
}

Arrys.copyOf()方法

public static char[] copyOf(char[] original, int newLength) {char[] copy = new char[newLength];//拷贝数组System.arraycopy(original, 0, copy, 0,Math.min(original.length, newLength));return copy;
}

AbstractStringBuilder的append()方法源码的第六行,是将String对象里面char数组里面的内容拷贝到StringBuilder对象的char数组里面,代码如下:

str.getChars(0, len, value, count);

getChars()方法

public void getChars(int srcBegin, int srcEnd, char dst[], int dstBegin) {//中间省略了一些检查...System.arraycopy(value, srcBegin, dst, dstBegin, srcEnd - srcBegin);}

拷贝流程见下图

假设现在有两个线程同时执行了StringBuilder的append()方法,两个线程都执行完了第五行的ensureCapacityInternal()方法,此刻count=5。

这个时候线程1的cpu时间片用完了,线程2继续执行。线程2执行完整个append()方法后count变成6了

线程1继续执行第六行的str.getChars()方法的时候拿到的count值就是6了,执行char数组拷贝的时候就会抛出ArrayIndexOutOfBoundsException异常。

至此,StringBuilder为什么不安全已经分析完了。如果我们将测试代码的StringBuilder对象换成StringBuffer对象会输出什么呢?

当然是输出10000啦!

那么StringBuffer用什么手段保证线程安全的?这个问题你点进StringBuffer的append()方法里面就知道了。


推荐阅读:Java面试题汇总(208道)【END】
关注下方二维码,订阅更多精彩内容

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/546473.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python计算校验文件的MD5、SHA1、SHA256和CRC32,获取文件创建日期、修改日期和文件大小

main.py # -*- coding: utf-8 -*- import os from hashlib import md5, sha1, sha256 from zlib import crc32 import time from math import ceilclass Hash:def __init__(self, strFilePath):self

CC++中的qsort库函数

qsort() 参考&#xff1a;http://www.slyar.com/blog/stdlib-qsort.html qsort包含在<stdlib.h>头文件中&#xff0c;此函数根据你给的比较条件进行快速排序&#xff0c;通过指针移动实现排序。排序之后的结果仍然放在原数组中。使用qsort函数必须自己写一个比较函数。 …

面试官:HTTPS 为什么是安全的?说一下他的底层实现原理?

作者 | leapmie来源 | urlify.cn/zQj6f2这篇干货不错&#xff0c;把HTTPS的原理讲清楚了&#xff0c;而且容易懂&#xff0c;建议大家好好读一下。# HTTPS随着 HTTPS 建站的成本下降&#xff0c;现在大部分的网站都已经开始用上 HTTPS 协议。大家都知道 HTTPS 比 HTTP 安全&…

PyQt5在对话框中打开外部链接的方法

利用PyQt5部分控件的Link属性链接 PyQt5有几个控件带有 setOpenExternalLinks &#xff0c; 如 QLabel、QTextLabel 、 QTextBrowser 等 当 setOpenExternalLinks 值为TURE 表示可通过html 添加 A 标签打开外部链接, 如设置&#xff1a; 我测试的是 QLabel 标签控件 self.lab…

第25周二

今天终于弄好集成测试环境&#xff0c;因为几个问题中间走了弯路&#xff0c;找到最后发现远程配置不成功是因为我没有向CMS发心跳&#xff0c;而原因是没有在spring的bean配置文件中加入心跳相关类&#xff0c;另一个问题访问没权限&#xff0c;是因为appCode类型的大小写问题…

面试官:为什么 Spring 中的 bean 默认为单例?

作者 | 小小木来源 | http://1t.click/ksQ熟悉Spring开发的朋友都知道Spring提供了5种scope分别是singleton、prototype、request、session、global session。如下图是官方文档上的截图&#xff0c;感兴趣的朋友可以进去看看这五种分别有什么不同。今天要介绍的是这五种中的前两…

博主推荐【文件Hash校验工具V1.0 -免费版】

文件Hash校验工具有什么用途&#xff1f; ​Hash校验工具可以用来计算文件的MD5、SHA1、SHA256、CRC32值。简单来说&#xff0c;MD5值就是文件的身份ID&#xff0c;并且具有唯一性。通过比对MD5值&#xff0c;用户能够检查文件是否被篡改过&#xff0c;确保安全性。一般来说&a…

spring AOP实现——xml方法

上一文中 讲了Annotation如何配置AOP&#xff0c;地址如下&#xff1a;http://5148737.blog.51cto.com/5138737/1428048使用同样的bean&#xff0c;用xml来实现一下&#xff1a;Hello.java 接口定义了三个方法&#xff1a;package com.xj.bean.aop;public interface Hello {pub…

基于深度学习的瓷砖色差分类方法研究——学习笔记(评价:色差的定义太模糊。。。问题描述不清楚,太水了)

文章目录 摘要0 引言1 瓷砖图像处理1.1 图像采集1.2 图像处理 2 基于深度学习的瓷砖色差分类算法设计2.1 数据预处理2.2 卷积神经网络的设计2.3 实验设计 3 瓷砖色差分类平台的设计与实现 摘要 瓷砖是人类建筑不可或缺的一种材料&#xff0c;而瓷砖品质最重要的指标之一就是色…

面试官 | 讲一下如何给高并发系统做限流?

作者 | nick hao来源 | uee.me/cDuRD在开发高并发系统时有三把利器用来保护系统&#xff1a;缓存、降级和限流。本文结合作者的一些经验介绍限流的相关概念、算法和常规的实现方式。缓存缓存比较好理解&#xff0c;在大型高并发系统中&#xff0c;如果没有缓存数据库将分分钟被…

Python利用multiprocessing实现多进程,Pyinstaller打包python多进程程序出现多个窗口

一、为什么需要采用multiprocessing多线程技术 自己在做文件Hash校验工具V1.0小工具软件时,需要读取文件,计算文件的MD5、SHA1、SHA256和CRC32这些Hash值,对于小文件能够很快计算出hash值,但是对于大文件需要花费一些时间,不知道进度如何?使用进度条指示也无法正确显示进…

二鸟在林不如一鸟在手

看一篇论文&#xff0c;总能引出一大堆相关的书籍 相关的领域&#xff0c;令人目不暇接&#xff0c;尤其是数学是需要证明和计算的&#xff0c;对初进入领域的研究人员是必要的 熟练之后才不必拘泥于细节&#xff0c;看这些文献、书感觉好像总没有尽头&#xff0c;看着看着就沉…

面试官 | 说一下数据库如何分库分表?

作者 | butterfly100来源 | cnblogs.com/butterfly100/p/9034281.html一. 数据切分关系型数据库本身比较容易成为系统瓶颈&#xff0c;单机存储容量、连接数、处理能力都有限。当单表的数据量达到1000W或100G以后&#xff0c;由于查询维度较多&#xff0c;即使添加从库、优化索…

Python对 txt 文件进行读写、清除和删除操作

一、文件读写 1.推荐使用:通过 with open( ) as f: 来打开文件,这种方法会自动关闭文件 文件操作模式表: ‘r’ 读取模式(默认值) ‘w’ 写入模式 ‘x’ 独占写入模式 ‘a’ 附加模式 ‘b’ 二进制模式(与其他模式结合使用) ‘t’ 文本模式(默认值,与其他模式结合使…

面试官 | JVM 为什么使用元空间替换了永久代?

7:40到11:40历时4个小时完成了该文&#xff0c;看到电脑中左边的便签了么&#xff0c;我也是拼了。在Java8和以后版本中JVM的内存结构慢慢发生了变化。作为面试官如果你还不知道&#xff0c;那么面试过程中是不是有些露怯&#xff1f;作为面试者&#xff0c;如果知晓这些变化&a…

jquery将表单序列化json对象

$.fn.serializeObject function () {var obj {};var count 0;$.each(this.serializeArray(), function (i, o) {var n o.name, v o.value;count;obj[n] obj[n] undefined ? v: $.isArray(obj[n]) ? obj[n].concat(v): [obj[n], v];});//obj.nameCounts count "…

Typora颠覆写作体验的极简好用 Markdown 编辑器基本设置教程

Typora是一款Markdown编辑器。 无论你是建网站写博客、每天写日记、自媒体写稿、办公、程序员写代码文档等等&#xff0c;Typora 都能满足你的要求。 Typora基本设置教程 1.“通用”项设置 打开“文件”下的“偏好设置”选项&#xff0c;在“通用”这项下&#xff0c;设置自…

面试官问:一个Java字符串中到底能有多少个字符?

作者 | 鸟窝来源 | urlify.cn/qYNR3q依照Java的文档&#xff0c; Java中的字符内部是以UTF-16编码方式表示的&#xff0c;最小值是 \u0000 (0),最大值是\uffff(65535)&#xff0c; 也就是一个字符以2个字节来表示&#xff0c;难道Java最多只能表示 65535个字符&#xff1f;char…

PHP多进程处理并行处理任务实例

2019独角兽企业重金招聘Python工程师标准>>> 本文目的 本文通过例子讲解linux环境下&#xff0c;使用php进行并发任务处理&#xff0c;以及如何通过pipe用于进程间的数据同步。写得比较简单&#xff0c;作为备忘录。 PHP多进程 通过pcntl_XXX系列函数使用多进程功能…

Python PyCharm利用PyQt5使QPlainTextEdit支持拖放文件,类提升,重写QPlainTextEdit类

一、利用PyCharm新建基于PyQt5对话框工程MyMainTest,添加QPlainTextEdit控件,保存主窗口MyQTMainForm.ui文件运行如下: 二、新建myqplaintextedit.py文件,创建MyQPlainTextEdit类继承于QPlainTextEdit,只允许excel(.xls或.xlsx)文件拖放,及信号发射处理。代码如下: #…