共享内存之不可变

目录

一、不可变设计

二、享元模式

三、final原理


一、不可变设计

String是怎么保证不可变的?

首先String类里面有个char数组value,是用final修饰的,所以只有在构造的时候能给他赋值,以后就没机会改变他的引用了。

有个变量hash,用来缓存字符串的哈希码,只有首次调用字符串时才会生成,后面就缓存再变量里避免后面计算,私有的没set方法所以没办法改变。

类上也加了final修饰保证了该类中的方法不能被覆盖,防止系列无意间破坏不可变性

public final class String
     implements java.io.Serializable, Comparable, CharSequence {
     /** The value is used for character storage. */
     private final char value[];

     /** Cache the hash code for the string */
     private int hash; // Default to 0
 
     // ...
}

final只能保证引用不变,他是怎么保证里面的数据不改变的呢?

看下面代码可见他的构造器是采用复制的方式来构建一个的,防止别的地方改了这个引用对象的值从而改了String(这种思想叫:保护性拷贝

public String(@NotNull String original){
    this.value = original.value;
    this.hash = original.hash;
}

public String(@NotNull char value[]){
    this.value = Arrays.copyOf(value,value.length);
}

可见在substring的源码也是如此,他返回的时候也是new了个新的来返回,这样substring并没有改动原来的字符串对象

public String substring(int beginIndex) {
     if (beginIndex < 0) {
         throw new StringIndexOutOfBoundsException(beginIndex);
     }
     int subLen = value.length - beginIndex;
     if (subLen < 0) {
         throw new StringIndexOutOfBoundsException(subLen);
     }
     return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}

public String(char value[], int offset, int count) {
     if (offset < 0) {
         throw new StringIndexOutOfBoundsException(offset);
     }
     if (count <= 0) {
         if (count < 0) {
             throw new StringIndexOutOfBoundsException(count);
         }
         if (offset <= value.length) {
             this.value = "".value;
             return;
         }
     }
     if (offset > value.length - count) {
         throw new StringIndexOutOfBoundsException(offset + count);
     }
     this.value = Arrays.copyOfRange(value, offset, offset+count);
}

这种通过创建副本对象来避免共享的手段称之为保护性拷贝

二、享元模式

由于保护性拷贝每次都会复制对象,就可以用享元模式来解决,他是23种设计模式之一,用在当需要重用数量右下的同一类对象时

JDK中Boolean、Byte、Short、Long、Integer、Character等包装提供了valueOf方法,例如Long的valueOf会缓存-128-127之间的Long对象,在这个范围之间会重用对象,大于这个范围才会新建Long对象

public static Long valueOf(long l) {
    final int offset = 128;
    if (l >= -128 && l <= 127) { // will cache
        return LongCache.cache[(int)l + offset];
    }
    return new Long(l);
}

是怎么实现这个缓存的呢? 他有个静态内部类LongCache初始化会创建大小为256的数组,然后初始化代码块会事先给他们赋值,之后调用valueOf直接从数组里获取,避免对象的重复创建

注意:Byte、Short、Long的缓存范围都是-128-127,Character缓存的是0-127,Integer的默认是-128-127最小的是不能变,但是这个127是可以调整参数Djava.lang.Integer.integerCache.high来改变。Boolean缓存了true和false

三、final原理

final的设置变量的原理,如果一个成员变量别标识为final,那么他在赋值的时候会加入写屏障,他可以保证写屏障之前的都不会重排序到写屏障之后

获取final变量的原理:如果final的变量是享元缓存范围内的会直接去栈内存中拿,如果是享元缓存外的会去常量池里面拿,如果不在final修饰,他都是在堆中拿

你可能感兴趣的:(java,开发语言,JUC,JVM,内存)