MeowRain
Life is SimpleLife is Simple
meowrain 发布于 收录于 Java 深入理解String、StringBuffer和StringBuilder类的区别 String类 String类是一个不可变类,即创建String对象后,该对象中的字符串是不可改变的,直到这个对象被销毁。存储在JVM中的字符串常量池中,当创建一个新的String对象时,如果该字符串已经存在于常量池中,则直接引用该对象,否则创建一个新的对象。
String是字符串常量,而StringBuffer和StringBuilder是字符串变量。由String创建的字符内容是不可改变的,而由StringBuffer和StringBuidler创建的字符内容是可以改变的。
StringBuffer类 StringBuffer是旧版本JDK中提供的一个可变字符串类。它是线程安全的,适用于多线程环境。StringBuffer的主要特点是:
可变性:可以修改字符串内容而不创建新的对象。
线程安全:所有方法都被synchronized修饰,保证了多线程环境下的安全性。
性能:由于线程安全的特性,性能相对较低。
StringBuilder类 StringBuilder是JDK 5引入的一个可变字符串类。它与StringBuffer类似,但不保证线程安全,因此在单线程环境下性能更高。StringBuilder的主要特点是:
可变性:可以修改字符串内容而不创建新的对象。 线程不安全:不保证线程安全,适用于单线程环境。 性能:由于不需要同步,性能更高。 String为什么不可变? String 类压根就没有提供任何 public 方法来修改那个 private final 的数组。你翻遍 String 类的所有方法,也找不到一个像 setValue(int index, char newChar) 这样的方法。
为什么String要设计成不可变 实现字符串常量池 (String Pool) 目的:这是最核心的原因,为了提升性能和节省内存。JVM 中存在一个字符串常量池,可以存放字符串对象。当创建多个值相同的字符串时,它们可以指向常量池中的同一个对象,从而避免重复创建。 前提:只有当字符串是不可变的,这种共享机制才是安全的。如果字符串是可变的,那么修改其中一个引用指向的内容,就会导致所有指向该对象的引用都受到影响,从而引发严重的程序逻辑混乱。
保证安全性 (Security) 场景:字符串在程序中被广泛用于存储敏感信息,比如数据库的用户名、密码、网络请求的地址等。 优势:不可变性可以防止这些关键值在创建后被意外或恶意篡改。例如,一个方法接收字符串参数进行安全校验,如果字符串是可变的,那么在校验通过后,其内容仍可能被外部修改,从而绕过安全检查。不可变性杜绝了这种风险。
天生线程安全 (Thread Safety) 场景:在多线程环境下,共享数据通常需要加锁等同步措施来避免冲突。 优势:因为 String 对象的状态永远无法被改变,所以它天生就是线程安全的。开发者可以放心地在多个线程之间共享同一个 String 对象,而无需任何额外的同步处理,这不仅简化了并发编程,也提高了程序的运行效率。
缓存哈希值 (Caching HashCode) 场景:字符串经常被用作 HashMap 或 HashSet 等集合的键(Key)。 优势:因为字符串不可变,所以它的哈希值(HashCode)也是固定不变的。String 类可以将计算出的哈希值缓存起来,当再次需要时直接返回,而不用重复计算。这保证了在使用 String 作为 Key 时,能够稳定、高效地在集合中进行存取操作。如果哈希值会变,集合的内部结构就会被破坏。
meowrain 发布于 收录于 Java 深入理解Java拷贝 浅拷贝(Shallow Copy) 浅拷贝会创建一个对象,对于原始对象(被拷贝的对象)中的基本数据类型字段,浅拷贝会直接复制这些值,对于引用类型的字段,浅拷贝会复制这个引用类型字段的地址,而不是复制这个引用类型字段所指向的对象本身。这意味着,如果原始对象中的引用类型字段指向一个对象,那么浅拷贝后的新对象中的相同字段仍然指向同一个对象。
这种情况下,我们如果修改了新对象中的引用类型字段所指向的对象,那么原始对象中的相同字段也会受到影响,因为它们指向的是同一个对象。
举个例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package org.example; public class Main { public static void main(String[] args) throws CloneNotSupportedException { Address addr = new Address("Beijing"); Person p1 = new Person("Alice", addr); Person p2 = (Person) p1.
meowrain 发布于 收录于 Java hashCode重写规则 两个对象相同,hashCode一定相等 两个对象不等,hashCode可以相等也可以不相等 hashCode相等的两个对象不一定相等 hashCode不等的两个对象一定不相等 为什么重写equals方法就要同时重写hashcode 在Java中,equals方法用于比较两个对象是否相等,而hashCode方法用于返回对象的哈希码。根据Java的规范,如果两个对象通过equals方法被认为是相等的,那么它们的hashCode方法必须返回相同的值。这是因为在使用哈希表(如HashMap、HashSet等)时,哈希码用于快速定位对象的位置。如果两个相等的对象有不同的哈希码,那么在哈希表中可能会导致查找失败或数据不一致。
简单说,就是map集合里面,因为要用到hashCode方法计算key,所以自定义对象就算里面的值相同,也会计算出不同的hashCode值,从而让相同的数据存储到不同的桶中。 这样的话,就会导致set集合里面的元素无法去重,map集合里面的key无法覆盖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package org.example; import java.util.*; //TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or // click the <icon src="AllIcons.
meowrain 发布于 收录于 Java 升级过程:
无锁 → 偏向锁 → 轻量级锁 → 重量级锁
https://juejin.cn/post/6988869083521351711
无锁 没有开启偏向锁的状态,JDK1.6之后偏向锁是默认开启的,但是还有一个偏向延迟,需要在JVM启动之后的多少秒之后才能开启,这个可以通过JVM参数进行设置,同时是否开启偏向锁也可以通过JVM参数设置
偏向锁 在偏向锁开启之后锁的状态,如果没有一个线程拿到这个锁的话,这个状态叫做匿名偏向,当一个线程拿到偏向锁的时候,下次想要竞争锁,只需要拿线程ID跟markword当中存储的线程ID进行比较,如果线程ID相同就直接获取锁(相当于锁偏向这个线程),不需要进行CAS操作和将线程挂起的操作。
轻量级锁 这个状态下线程主要是通过CAS操作实现的,将对象的Markword存储到线程的虚拟机栈上,然后通过CAS将对象的markword内容设置为指向Displaced Mark Word的指针,如果设置成功则获取锁。
在线程处临界区的时候,也需要使用CAS,如果CAS替换成功则同步成功,如果失败表示有其他线程在获取锁,那么就需要在释放锁之后将被挂起的线程唤醒。
重量级锁 如果轻量级锁自旋到达阈值后仍未获取到锁,就会升级为重量级锁。这是因为CAS操作失败时会持续自旋,进行while循环操作,非常消耗CPU资源。而升级为重量级锁后,线程会被操作系统调度并挂起,从而节约CPU资源
升级过程
meowrain 发布于 收录于 Java 什么是公平锁与非公平锁? 公平锁 指多个线程按照申请锁的顺序来获取锁,线程直接进入队列中排队,队列中的第一个线程才能获得锁。公平锁的优点在于各个线程公平地获取锁,避免了线程饥饿的情况。
非公平锁 指线程在获取锁时不按照申请锁的顺序,而是直接尝试获取锁,如果锁被其他线程持有,则会直接返回失败。非公平锁的优点在于性能更高,因为线程不需要排队等待,可以直接尝试获取锁。解决了线程饥饿的问题,但可能会导致某些线程长时间无法获取锁。
非公平锁吞吐量为什么比公平锁大? 非公平锁的吞吐量 之所以高,是因为它减少了线程上下文切换的开销,允许新来的线程插队,避免了唤醒等待线程中线程锁必须的系统开销。
meowrain 发布于 收录于 Java volatile可以保证线程安全吗? volatile关键字能保证可见性,但是不能保证原子性,因此不能完全保证线程安全。volatile关键字用于修饰变量,当一个线程修改了volatile修饰的值,其他线程能看到最新的值,避免了线程之间的不一致。
但是volatile不能解决多线程并发下的复合操作问题,比如i++这种操作就不是原子操作,如果多个线程同时对i进行自增操作,volatile不能保证线程安全。对于复合操作,需要使用synchronized或Lock等机制来保证线程安全。
meowrain 发布于 收录于 Java 指令重排序的原理是什么?
meowrain 发布于 收录于 Java 1. 保证变量对所有线程的可见性 当一个变量被声明为volatile的时候,它会保证对这个变量的写操作会立即刷新到主存中,而对这个变量的读操作会直接从主存中读取,从而确保了多线程环境下对该共享变量访问的可见性。这意味着一个线程修改了volatile变量的值,其它线程能够立刻看到这个修改,不会受到各自线程工作内存的影响。
2. 禁止指令重排序 volatile关键字在java中主要通过内存屏障来禁止特定类型的指令重排序‘
写写屏障 在 对volatile变量执行写操作之前,会插入一个写屏障,确保了在该便写操作之前的所有普通写操作都已完成,防止了这些写操作被移到volatile写操作之后。
读写屏障 在对volatile变量执行读操作后,会插入一个读屏障,它确保了对volatile变量的读操作之后的所有普通读操作都不会被提前到volatile读之前执行,保证了读到的数据都是最新的。
写读屏障 发生在volatile写之后和volatile读之前,这个屏障确保了volatile写操作之前的所有内存操作都不会被重排序到,也确保了volatile读操作之后的所有内存操作都不会被重排序到volatile写之前。
meowrain 发布于 收录于 Java ThreadLocal对象内部有一个ThreadLocalMap类型的成员变量,这个Map的生命周期和线程本身是绑定的,只要线程不销毁,这个Map就会一直存在。
ThredLocalMap存储数据用的Entry继承了WeakReference,它的结构是这样的: key是对ThreadLocal对象的弱引用,而value是我们存入的值,它是强引用。
内存泄漏问题 当一个ThreadLocal对象在外部没有了强引用(比如方法执行结束),下一次GC发生的时候,由于Entry的key是弱引用,所以ThreadLocal对象会被回收。 这时,ThreadLocalMap中就会出现key为Null的Entry,这个Entry的value仍然是强引用的,这就导致了内存泄漏。只要线程不结束,ThreadLocalMap就会一直持有这个值,无法被回收,Entry会一直持有这个value的强引用,导致value无法被垃圾回收。如果线程是从线程池中复用的,那么这个线程的生命周期会很长,这些value就会极少称多,最终导致内存泄漏。
因此,为了防止ThreadLocal内存泄漏,在使用完ThreadLocal之后,必须在代码中调用ThreadLocal的remove()方法来清除当前线程中的ThreadLocal变量。
meowrain 发布于 收录于 Java ThreadLocal的原理 ThreadLocal的实现依赖于Thread类中的一个ThreadLocalMap字段,这是一个存储ThreadLocal变量本身和对应值的映射。
每个线程都有自己的ThreadLocalMap实例,用于存储该线程所持有的所有ThreadLocal变量的值。
当你创建一个ThreadLocal变量的时候,它实际上就是一个ThreadLocal对象的实例,每个ThreadLocal对象都可以存储任意类型的值,这个值对于每个线程来说是独立的。
get()、set()和remove()方法 get()方法 当调用ThreadLocal的get()方法的时候,ThreadLocal会检查当前线程的ThreadLocalMap中是否有与值关联的值。
如果有,就返回该值
如果没有,会调用initialValue()方法初始化该值,然后将其放入ThreadLocalMap中并返回。
如果之前没有设置过值,ThreadLocal会调用initialValue()方法来获取一个默认值。 举个例子:
1 2 3 4 5 6 7 ThreadLocal<String> threadLocal = new ThreadLocal<String>() { @Override protected String initialValue() { return "default value"; } }; String value = threadLocal.get(); // 如果没有设置过值,将返回"default value" 如果当前线程的ThreadLocalMap中没有与该ThreadLocal对象关联的值,get()方法会调用ThreadLocal的initialValue()方法来获取一个默认值,并将这个值存储在ThreadLocalMap中。 如果ThreadLocalMap中已经存在与该ThreadLocal对象关联的值,get()方法会直接返回这个值,而不会调用initialValue()方法。
set()方法 当调用set()方法的时候,ThreadLocal会将给定值与当前线程管来弄起来 也就是在ThreadLocalMap中存储一个键值对,key是ThreadLocal对象本身,value是传入的值。
remove()方法 当调用remove()方法的时候,ThreadLocal会从当前线程的ThreadLocalMap中删除与该ThreadLocal对象关联的值。