synchronized
是java 中元老级的锁。他的使用非常的简单, 可以作用在普通方法,静态方法以及方法块上。对象是 synchronized
实现同步的基础,java 中每个对象都可以作为锁。具体表现为以下三种方式:
1. 对于普通方法, 锁是当前对象的实例
2. 对于静态同步方法, 锁是当前类的 Class 对象
3. 对于同步方法块, 锁是 synchronized 括号里面配置的对象
今天代码review 的时候, 发现一个新奇的写法,简化后的代码如下:
public static Object get(String key){
String syncKey = key + "something else";
synchronized (syncKey){
// 简化的逻辑
// obj = map.get(syncKey)
// if obj == null
// obj = Provider.get()
// map.put(obj)
// return obj
}
}
咋一看这跟我们常规的写法不一样,常规的写法 一般是将 syncKey
定义为类的一个属性, 这样能够保证多线程确实是基于同一个对象实例来做同步的。对于这个写法作者的解释是 java 对于String 是做过特殊处理的,值一样的String 是会做缓存的。所以多线程访问的情况下仍然是作用在同一个对象上, 可以达到同步的效果。
对于这个结论是一半对一半不对, 具体能不能达到想要的同步效果,就要看是不是作用在同一个对象实例上。那String 到底是不是同一个对象实例呢? 那就要从 String 的内存模型说起了。具体详解可以参考深入理解Java:String
所以针对这个示例,其实是不对的。 String syncKey = key + "something else"
每次都返回一个 new String()
对于 synchronized
来说肯定不是同一个对象实例, 所以就达不到同步的效果。但是如果修改成如下的就是可以的:
public static Object get(String key){
String syncKey = "something else";
synchronized (syncKey){
// 简化的逻辑
// obj = map.get(syncKey)
// if obj == null
// obj = Provider.get()
// map.put(obj)
// return obj
}
}
String syncKey = "something else";
这样的方式是将 something else
这个字符串保存到了JVM 的字符串常量池中, 并且可以共享。所以多线程访问的时候拿到的都是同一个对象, 就能达到同步的效果。
Java 还有没有能够达到这种效果的对象吗?答案是肯定的, 以下的几种情况得到的都是同一个对象实例:
1. byte(Byte), short(Short), int(Integer), long(Long) 值在[-128,127] 之间
2. boolean (true or false)
3. char 值在 ['\u0000' - '\u007f']
这个是Java 语言规范中定义的。这些值会在JVM中缓存下来, 每次使用都会返回同一个实例。但是仅限Integer i = 1
这样的方式. 如果是 Integer i = new Integer(1)
的话, 那就是不同的对象了。
结论
synchronized
将锁标记保存在对象头中, 所以只要对象是同一个就能够达到同步的效果,也不一定要是实例变量。而Java 语言中某些对象实例虽然是局部变量, 但是由于一些性能优化上的考虑, 会对String, Byte,Short,Boolean,Integer,Long 类型的数据做一些优化。