数据溢出
整型中,每个类型都有一定的表示范围,但是,在程序中有些计算会导致超出表示范围,即溢出
/**
* 数据溢出
* byte:byte用1个字节来存储,范围为-128(-2^7)到127(2^7-1),在变量初始化的时候,byte类型的默认值为0。
* short:short用2个字节存储,范围为-32,768 (-2^15)到32,767 (2^15-1),在变量初始化的时候,short类型的默认值为0,一般情况下,因为Java本身转型的原因,可以直接写为0。
* int:int用4个字节存储,范围为-2,147,483,648 (-2^31)到2,147,483,647 (2^31-1),在变量初始化的时候,int类型的默认值为0。
* long:long用8个字节存储,范围为-9,223,372,036,854,775,808 (-2^63)到9,223,372,036, 854,775,807 (2^63-1),在变量初始化的时候,long类型的默认值为0L或0l,也可直接写为0。
*/
public static void valueOverflow(){
int a = Integer.MAX_VALUE;
int b = Integer.MAX_VALUE;
int addResult = a+b;
System.out.println("a = "+ a +",b = " + b + " ,a+b = " + addResult);
}
计算结果
自动拆装箱与缓存
public static void main(String[] args) {
Integer integer1 = 3;
Integer integer2 = 3;
if (integer1 == integer2){
System.out.println("integer1 == integer2");
}else {
System.out.println("integer1 != integer2");
}
Integer integer3 = 300;
Integer integer4 = 300;
if (integer3 == integer4){
System.out.println("integer3 == integer4");
}else {
System.out.println("integer3 != integer4");
}
}
执行结果
Java中,==比较的是对象应用,equals比较的是值。
产生上述结果的原因
Integer中的缓存机制。Java5中,在Integer的操作上引入了一个新的功能来节省内存和提高性能。整型对象通过使用相同的对象引用实现了缓存和重用。
自动装箱本质
Integer i = 100;
Integer i_truth = Integer.valueOf(100);
PS:两条命令相等
valueOf Java底层实现
在创建对象之前先从IntegerCache.cache中寻找。如果没找到才使用new新建对象。
IntegerCache Class
IntegerCache是Integer类中定义的一个private static的内部类。
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
其中的javadoc详细的说明了缓存支持-128到127之间的自动装箱过程。最大值127可以通过-XX:AutoBoxCacheMax=size修改。 缓存通过一个for循环实现。从低到高并创建尽可能多的整数并存储在一个整数数组中。这个缓存会在Integer类第一次被使用的时候被初始化出来。以后,就可以使用缓存中包含的实例对象,而不是创建一个新的实例(在自动装箱的情况下)。
实际上这个功能在Java 5中引入的时候,范围是固定的-128 至 +127。后来在Java 6中,可以通过java.lang.Integer.IntegerCache.high设置最大值。这使我们可以根据应用程序的实际情况灵活地调整来提高性能。到底是什么原因选择这个-128到127范围呢?因为这个范围的数字是最被广泛使用的。 在程序中,第一次使用Integer的时候也需要一定的额外时间来初始化这个缓存。
PS:
适用于整数值区间-128~+127
只适用于自动装箱。使用构造函数创建对象不适用。
其他缓存的对象
这种缓存行为不仅适用于Integer对象。我们针对所有的整数类型的类都有类似的缓存机制。
ByteCache用于缓存Byte对象
ShortCache用于缓存Short对象
LongCache用于缓存Long对象
CharacterCache用于缓存Character对象
Byte, Short, Long有固定范围: -128 到 127。对于Character, 范围是 0 到 127。除了Integer以外,这个范围都不能改变。
阿里巴巴《Java开发手册》明确说明:
包装类对象值比较
单例模式问题
单列模式——保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。
单列模式 VS 序列化、反序列化
/**
* 双重校验单列类
*/
public class Singleton implements Serializable {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
测试类
public class SingletonTest {
public static void main(String[] args) throws IOException, ClassNotFoundException {
//Write Obj to file
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
oos.writeObject(Singleton.getSingleton());
//Read Obj from file
File file = new File("tempFile");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
Singleton newInstance = (Singleton) ois.readObject();
//判断是否是同一个对象
System.out.println(newInstance == Singleton.getSingleton());
}
}
结果:false
变种后的单列模式
/**
* 双重校验单列类
*/
public class Singleton implements Serializable {
private volatile static Singleton singleton;
private Singleton (){}
public static Singleton getSingleton() {
if (singleton == null) {
synchronized (Singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
private Object readResolve() {
return singleton;
}
}
PS:运行测试类 结果为 true
原因:序列化会通过反射调用无参数的构造方法创建一个新的对象
变种后的单例模式 通过在readResolve方法中指定要返回的对象的生成策略,即可防止单例被破坏。
SimpleDateFormat线程安全问题
阿里开发手册
SimpleDateFormat线程不安全验证
/**
* @author 4869
* SimpleDateFormat 线程不安全验证
*/
public class PoolThreadTest {
/**
* 定义一个全局的SimpleDateFormat
*/
private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
/**
* 使用ThreadFactoryBuilder定义一个线程池
*/
private static ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
private static ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
/**
* 定义一个CountDownLatch,保证所有子线程执行完之后主线程再执行
*/
private static CountDownLatch countDownLatch = new CountDownLatch(100);
public static void main(String[] args) throws Exception{
//定义一个线程安全的HashSet
Set dates = Collections.synchronizedSet(new HashSet());
for (int i = 0; i < 100; i++) {
//获取当前时间
Calendar calendar = Calendar.getInstance();
int finalI = i;
pool.execute(() -> {
//时间增加
calendar.add(Calendar.DATE, finalI);
//通过simpleDateFormat把时间转换成字符串
String dateString = simpleDateFormat.format(calendar.getTime());
//把字符串放入Set中
dates.add(dateString);
//countDown
countDownLatch.countDown();
});
}
//阻塞,直到countDown数量为0
countDownLatch.await();
//输出去重后的时间个数
System.out.println(dates.size());
// System.exit(0);
}
}
PS : 不进行手动停止 主线程会一直处于阻塞状态;原因是启动的线程数小于100, countDown 不可能为0;
线程不安全的原因:
SimpleDateFormat中的format方法在执行过程中,会使用一个成员变量calendar来保存时间。声明SimpleDateFormat的时候,使用的是static定义的。那么SimpleDateFormat就是一个共享变量,SimpleDateFormat中的calendar也就可以被多个线程访问到。
解决原理:不要把SimpleDateFormat作为一个共享变量使用。
解决方法:1. SimpleDateFormat 变成局部变量使用 2. 加同步锁 3. ThreadLocal
JDK8 提供如下解决方案:可以使用Instant代替Date,LocalDateTime代替Calendar,DateTimeFormatter代替SimpleDateFormat,官方给出的解释:simple beautiful strong immutable thread-safe。