string 释放_由String,String Builder,String Buffer 引起的面试惨案
前言
String,StringBuilder,StringBuffer的区别是啥?这个面试题估计每个JAVA都应该碰到过吧。依稀记得第一次面试的时候,面试官问我这个问题时,心想着能有啥区别不都是拼接字符串嘛。深入了解这个问题后,发现并不简单?
前菜
面试官:你好,你是不一样的科技宅是吧?
小宅:面试官你好,我是不一样的科技宅。
面试官:你好,麻烦做一个简单的自我介绍吧。
小宅:我叫不一样的科技宅,来自xxx,做过的项目主要有xxxx用到xxx,xxx技术。
面试官:好的,对你的的履历有些基本了解了,那我们先聊点基础知识吧。
小宅:内心OS(放马过来吧)
开胃小菜
面试官:String,StringBuilder,StringBuffer的区别是啥?
小宅:这个太简单了吧,这是看不起我?
- 从可变性来讲String的是不可变的,StringBuilder,StringBuffer的长度是可变的。
- 从运行速度上来讲StringBuilder > StringBuffer > String。
- 从线程安全上来StringBuilder是线程不安全的,而StringBuffer是线程安全的。
所以 String:适用于少量的字符串操作的情况,StringBuilder:适用于单线程下在字符缓冲区进行大量操作的情况,StringBuffer:适用多线程下在字符缓冲区进行大量操作的情况。
面试官:为什么String的是不可变的?
小宅:因为存储数据的char数组是使用final进行修饰的,所以不可变。
面试官:刚才说到String是不可变,但是下面的代码运行完,却发生变化了,这是为啥呢?
public class Demo {public static void main(String[] args) {String str = "不一样的";str = str + "科技宅";System.out.println(str);}}
很明显上面运行的结果是:不一样的科技宅。
我们先使用javac Demo.class
进行编译,然后反编译javap -verbose Demo
得到如下结果:
public static void main(java.lang.String[]);descriptor: ([Ljava/lang/String;)Vflags: ACC_PUBLIC, ACC_STATICCode:stack=2, locals=2, args_size=10: ldc #2 // String 不一样的2: astore_13: new #3 // class java/lang/StringBuilder6: dup7: invokespecial #4 // Method java/lang/StringBuilder."<init>":()V10: aload_111: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;14: ldc #6 // String 科技宅16: invokevirtual #5 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;19: invokevirtual #7 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;22: astore_123: getstatic #8 // Field java/lang/System.out:Ljava/io/PrintStream;26: aload_127: invokevirtual #9 // Method java/io/PrintStream.println:(Ljava/lang/String;)V30: return
我们可以发现,在使用+
进行拼接的时候,实际上jvm是初始化了一个StringBuilder
进行拼接的。相当于编译后的代码如下:
public class Demo {public static void main(String[] args) {String str = "不一样的";StringBuilder builder =new StringBuilder();builder.append(str);builder.append("科技宅");str = builder.toString();System.out.println(str);}}
我们可以看下builder.toString();
的实现。
@Override
public String toString() {// Create a copy, don't share the arrayreturn new String(value, 0, count);
}
很明显toString
方法是生成了一个新的String
对象而不是更改旧的str
的内容,相当于把旧str
的引用指向的新的String
对象。这也就是str
发生变化的原因。
分享我碰到过的一道面试题,大家可以猜猜答案是啥?文末有解析哦
public class Demo {public static void main(String[] args) {String str = null;str = str + "";System.out.println(str);}}
面试官:String类可以被继承嘛?
小宅:不可以,因为String类使用final关键字进行修饰,所以不能被继承,并且StringBuilder,StringBuffer也是如此都被final关键字修饰。
面试官:为什么String Buffer是线程安全的?
小宅:这是因为在StringBuffer
类内,常用的方法都使用了synchronized
进行同步所以是线程安全的,然而StringBuilder
并没有。这也就是运行速度StringBuilder
> StringBuffer
的原因了。
面试官:刚才你说到了synchronized
关键字 ,那能讲讲synchronized
的表现形式嘛?
小宅:
- 对于普通同步方法 ,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的class对象。
- 对于同步方法块,锁是Synchonized括号配置的对象。
面试官:能讲讲synchronized
的原理嘛?
小宅:synchronized
是一个重量级锁,实现依赖于JVM
的 monitor
监视器锁。主要使用monitorenter
和monitorexit
指令来实现方法同步和代码块同步。在编译的是时候,会将monitorexit
指令插入到同步代码块的开始位置,而monitorexit
插入方法结束处和异常处,并且每一个monitorenter
都有一个与之对应的monitorexit
。
任何对象都有一个monitor
与之关联,当一个monitor
被持有后,它将被处于锁定状态,线程执行到monitorenter
指令时间,会尝试获取对象所对应的monitor
的所有权,即获取获得对象的锁,由于在编译期会将monitorexit
插入到方法结束处和异常处,所以在方法执行完毕或者出现异常的情况会自动释放锁。
硬菜来了
面试官:前面你提到synchronized
是个重量级锁,那它的优化有了解嘛?
小宅:为了减少获得锁和和释放锁带来的性能损耗引入了偏向锁、轻量级锁、重量级锁来进行优化,锁升级的过程如下:
首先是一个无锁的状态,当线程进入同步代码块的时候,会检查对象头内和栈帧中的锁记录里是否存入存入当前线程的ID,如果没有使用CAS
进行替换。以后该线程进入和退出同步代码块不需要进行CAS
操作来加锁和解锁,只需要判断对象头的Mark word
内是否存储指向当前线程的偏向锁。如果有表示已经获得锁,如果没有或者不是,则需要使用CAS
进行替换,如果设置成功则当前线程持有偏向锁,反之将偏向锁进行撤销并升级为轻量级锁。
轻量级锁加锁过程,线程在执行同步块之前,JVM会在当前线程的栈帧中创建用于存储锁记录的空间,并将对象头的Mark Word
复制到锁记录(Displaced Mark Word
)中,然后线程尝试使用CAS
将对象头中的Mark Word
替换为指向锁记录的指针。如果成功,当前线程获得锁,反之表示其他线程竞争锁,当前线程便尝试使用自旋来获得锁。
轻量级锁解锁过程,解锁时,会使用CAS将Displaced Mark Word
替换回到对象头,如果成功,则表示竞争没有发生,反之则表示当前锁存在竞争锁就会膨胀成重量级锁。
升级过程流程图
白话一下:
可能上面的升级过程和升级过程图,有点难理解并且还有点绕。我们先可以了解下为什么会有锁升级这个过程?HotSpot的作者经过研究发现,大多数情况下锁不仅不存在多线程竞争,而且总是由同一个线程多次获得。为了避免获得锁和和释放锁带来的性能损耗引入锁升级这样一个过程。理解锁升级这个流程需要明确一个点:发生了竞争才锁会进行升级并且不能降级。
我们以两个线程T1,T2执行同步代码块来演示锁是如何膨胀起来的。我们从无锁的状态开始 ,这个时候T1进入了同步代码块,判断当前锁的一个状态。发现是一个无锁的状态,这个时候会使用CAS
将锁记录内的线程Id指向T1并从无锁状态变成了偏向锁。运行了一段时间后T2进入了同步代码块,发现已经是偏向锁了,于是尝试使用CAS
去尝试将锁记录内的线程Id改为T2,如果更改成功则T2持有偏向锁。失败了说明存在竞争就升级为轻量级锁了。
可能你会有疑问,为啥会失败呢?我们要从CAS
操作入手,CAS
是Compare-and-swap(比较与替换)的简写,是一种有名的无锁算法。CAS需要有3个操作数,内存地址V,旧的预期值A,即将要更新的目标值B,换句话说就是,内存地址0x01存的是数字6我想把他变成7。这个时候我先拿到0x01的值是6,然后再一次获取0x01的值并判断是不是6,如果是就更新为7,如果不是就再来一遍之道成功为止。这个主要是由于CPU的时间片原因,可能执行到一半被挂起了,然后别的线程把值给改了,这个时候程序就可能将错误的值设置进去,导致结果异常。
简单了解了一下CAS
现在让我们继续回到锁升级这个过程,T2尝试使用CAS
进行替换锁记录内的线程ID,结果CAS
失败了这也就意味着,这个时候T1抢走了原本属于T2的锁,很明显这一刻发生了竞争所以锁需要升级。在升级为轻量级锁前,持有偏向锁的线程T1会被暂停,并检查T1的状态,如果T1处于未活动的状态/已经退出同步代码块的时候,T1会释放偏向锁并被唤醒。如果未退出同步代码块,则这个时候会升级为轻量级锁,并且由T1获得锁,从安全点继续执行,执行完后对轻量级锁进行释放。
偏向锁的使用了出现竞争了才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。并且偏向锁的撤销需要等待全局安全点(这个时间点没有任何正在执行的字节码)。
T1由于没有人竞争经过一段时间的平稳运行,在某一个时间点时候T2进来了,产生使用CAS
获得锁,但是发现失败了,这个时候T2会等待一下(自旋获得锁),由于竞争不是很激烈所以等T1执行完后,就能获取到锁并进行执行。如果长时间获取不到锁则就可能发生竞争了,可能出现了个T3把原本属于T2的轻量级锁给抢走了,这个时候就会升级成重量级锁了。
吃完撤退
面试官:内心OS:竟然没问倒他,看来让他培训是没啥希望了,让他回去等通知吧 。
小宅是吧,你的水平我这边基本了解了,我对你还是比较满意的,但是我们这边还有几个候选人还没面试,没办法直接给你答复,你先回去等通知吧。
小宅:好的好的,谢谢面试官,我这边先回去了。多亏我准备的充分,全回答上来了,应该能收到offer了吧。
面试题解析
public class Demo {public static void main(String[] args) {String str = null;str = str + "";System.out.println(str);}}
答案是 null,从之前我们了解到使用+
进行拼接实际上是会转换为StringBuilder
使用append
方法进行拼接。所以我们看看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;
}private AbstractStringBuilder appendNull() {int c = count;ensureCapacityInternal(c + 4);final char[] value = this.value;value[c++] = 'n';value[c++] = 'u';value[c++] = 'l';value[c++] = 'l';count = c;return this;
}
从代码中可以发现,如果传入的字符串是null
时,调用appendNull
方法,而appendNull
会返回null。
结尾
我是不一样的科技宅,每天进步一点点,体验不一样的生活。我们下期见!
如果觉得对你有帮助,可以多多评论,多多点赞哦,也可以到我的主页看看,说不定有你喜欢的文章,也可以随手点个关注哦,谢谢。
string 释放_由String,String Builder,String Buffer 引起的面试惨案相关推荐
- java string 反序列化_如何将java.lang.String的空白JSON字符串值反序列化为null?
我正在尝试使用简单的JSON反序列化为Java对象.不过,我,让空 字符串 值,java.lang.String属性值.在其余的属性中,空白值将转换为 空 值(这是我想要的). 我的JSON和相关的J ...
- java string查找_查找输出程序(Java String类)
java string查找 Program 1 程序1 public class iHelp {public static void main (String[] args) {System.out. ...
- string 包含_一文搞懂String常见面试题,从基础到实战,到原理分析和源码解析...
01 string基础 1.1 Java String 类 字符串广泛应用 在 Java 编程中,在 Java 中字符串属于对象,Java 提供了 String 类来创建和操作字符串. 1.2 创建字 ...
- java string()函数_从Java中的String函数返回String构建器?
我有以下程序,我必须将字符串附加到另一个字符串,我使用字符串构建器以标准方式执行.但是,即使在将其转换为toString()之后,该函数也不允许我返回ab.我想问为什么? import java.ut ...
- java string 不变_为什么String在java中是不可变的?
什么是不可变对象? 众所周知, 在Java中, String类是不可变的.那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的.不 ...
- java stringbuffer原理_深入理解Java:String
在讲解String之前,我们先了解一下Java的内存结构. 一.Java内存模型 按照官方的说法:Java 虚拟机具有一个堆,堆是运行时数据区域,所有类实例和数组的内存均从此处分配. JVM主要管理两 ...
- tp5 聚合max获取不到string最大值_深入理解Kafka客户端之如何获取集群元数据
一.场景说明 当我们初始化一个Kafka生产者后(初始化流程可以查看<Kafka源码解析之生产者初始化流程>),通过该生产者将封装好的消息发送出去,示例代码仍然参考example模块下的P ...
- java 字符串是对象吗_解析Java中的String对象的数据类型
解析Java中的String对象的数据类型 2007-06-06 eNet&Ciweek 1. 首先String不属于8种基本数据类型,String是一个对象. 因为对象的默认值是null,所 ...
- json转string工具_不要再重复造轮子了,这款开源工具类库贼好使!
Hutool是一个小而全的Java工具类库,它帮助我们简化每一行代码,避免重复造轮子.如果你有需要用到某些工具类的时候,不妨在Hutool里面找找.本文总结了平时常用的16个工具类,希望对大家有所帮助 ...
最新文章
- java怎么求两组整数的或集,确定整数是否在具有已知值集的两个整数(包括)之间的最快方法...
- 22条API设计的最佳实践
- 如何免费使用数据挖掘软件RapidMiner - 申请学生许可证
- 精彩回顾 | Serverless Developer Meetup 12.04 深圳站
- OD点击寄存器变色OD
- 深度学习模型加速方法
- kd树的构造和搜索(超详细)
- Bootstrap 学习笔记1 - CSS
- 国王、总统、首相、总理……京城第一饭店的40春秋 | 美通社头条
- C语言课程设计(服装管理系统详解)
- 关于《0bug》一书随机数的一处修订
- Spring懒加载机制原理和配置讲解
- 【软件测试】白盒测试方法与黑盒测试方法的区别
- 有道笔记链接地址 -----关于python
- Linux df命令怎么使用
- 【Linux Socket 编程入门】05 - 拉个骡子溜溜:TCP编程模型代码分析
- php知识库管理系统,开源知识库管理系统
- 转:Markdown个性化主题设置
- 网红蛋糕店被关闭,我看到了一个暴利的赚钱项目
- 牛人们的面试心得与总结
热门文章
- 思科交换机配置试题_【干货】思科交换机路由器怎么配置密码?
- Java NIO学习篇之StandardOpenOption详解
- K-SVD字典学习算法
- java的动态绑定是什么意思_Java的动态绑定机制
- websphere配置oracle数据源,Websphere - 配置Oracle数据源
- oracle mysql 线程数_oracle线程数更改
- linux安装mysql默认的配置文件_[转]关于Linux安装mysql默认配置文件位置
- ARM汇编指令格式及规则
- How to: Create and Initialize Trace Listeners
- Java Class 文件结构