class Foo {
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
Helper h;
synchronized(this) {
h = helper;
if (h == null)
synchronized (this) {
h = new Helper();
}
helper = h;
}
}
return helper;
}
}上面这个类的锁是在何处释放的,这样的类释放线程安全。
private Helper helper = null;
public Helper getHelper() {
if (helper == null) {
Helper h;
synchronized(this) {
h = helper;
if (h == null)
synchronized (this) {
h = new Helper();
}
helper = h;
}
}
return helper;
}
}上面这个类的锁是在何处释放的,这样的类释放线程安全。
解决方案 »
- java怎么用7z的sdk解压一个7z文件,并保存到一个位置?
- java框架图标
- java或SWT中如何绘制图像并保存为图片
- POI 取Excel字体的问题(欢迎大家进来交流一下,在线等,。。。。ps:领导下达了死命令 诶)
- 关于JAVA Object 反序列化的问题,如何将一个格式化的String反序列化成对应的Java object
- Gui 如何利用JTbale实现关系并操作
- JTextPane如何识别选中的图形~~……!!!
- 在eclipse中的swt中,如何响应ALT+F4事件,在线等!!!!!!!!
- java中类中对变量的声明为什么放在类的结尾处而不是开头处
- 为什么会出错
- java 用C#.net生成的密钥进行加密的问题
- 请教怎么在java中做成像csdn左边的导航栏呢?
h = helper;
if (h == null)
synchronized (this) {//自己已经获得了锁,为什么还要再次获得?
h = new Helper();
}
helper = h;
}
}
1、
synchronized(this)
{
synchronized(this)
{
synchronized(this)
{
}
}
}
这样的代码确实没什么用,但java里也不禁止,同时编译后也没有做任何优化或变动。用反编译工具看一下就知道了。
2、
public Helper newHelper{
synchronized(this);
{
return new Helper();
}
}
public Helper getHelper() {
if (helper == null) {
Helper h;
synchronized(this) {
h = helper;
if (h == null)
h = newHelper();
helper = h;
}
}
return helper;
}这段代码把new Helper那段代码重新写成了一个函数。效果跟楼主的代码是一样的。但大家看起来就不会觉得那么奇怪了。
2.关于双重检查成例,你不知道那是你的问题。我不知道楼主从何处copy过来的代码,但这是一个双重检查成例的fix版本,被人拿来做例子讲的,一字不差。
3。双重检查成例的目的不是用来验证synchronized的机制的,它是C++中常用到的一个方法,作用相当与一个singleton,而在java中执行这样的代码却不能达到这样的目的,尽管代码从逻辑上看似乎行得通。这个是与java的内存模型与编译器原理有关。还是那句话,不知道就先去查一下。
public Helper newHelper{
synchronized(this);
{
return new Helper();
}
}
public Helper getHelper() {
if (helper == null) {
Helper h;
synchronized(this) {
h = helper;
if (h == null)
h = newHelper();
helper = h;
}
}
return helper;
}这段代码你觉得锁在哪释放?
h = newHelper();执行完后?
还是
helper = h;
} 之后?
这段代码的目的,其实是想实现一个singleton,在helper实例化之后就不再去创建一个新的,然而因为在java中修改h的reference和初始化Helper类的顺序并不确定,也就是说有可能h已经有了一个non-null的reference,但是new Helper()可能并未完成,这时候如果第二个线程进来,看到helper已经不是null,就返回了,而拿到的helper并没有初始化成功.
可能会有人问"help = h"这句话是在"synchronized(this){h = new Helper();}"之后发生的,那么就应该是new Helper()完了之后,才会将help的reference改变啊,这个就是synchronized机制的问题了,因为java中用synchronized的时候,当你获得一个锁,那么在你释放这个锁之前,synchronized中所有的action都要做完,而代码中内部的那个synchronized用的是和外部那个同一个锁,这会使java的编译器把"helper = h"的位置提前,以保证所有action在锁被释放之前发生.解决这个问题的一个方法是直接将getHelper()这个方法synchronized,当然这样一来会有一些性能的损失.
代码本身存在的问题在于第一个if (helper == null) 不在synchronized的保护之下,从纯粹的技术上考虑,可能会存在后面的helper = h 在没有完全付值的时候(一个object的reference在jvm里为4个byte,当helper没有完成4个byte的付值的时候),此时helper既不是null,也不可用,搞不好会使系统崩溃。这种现象在32位以上的单cpu系统是不可能发生的。多cup可能出现问题不用多解释,单cup的线程切换是建立在cup指令之上的。对于一个32位以上的cpu,一个4个byte的object的reference的付值是不会被线程切换中断的。至于new Helper()可能未完成,以及class未初始化完毕(或不可预知)的说法,只能说是对java及jvm的运行机制不够了解。关于某个文章引用了晦涩难懂的词汇也并未解释清楚这段代码的问题, 写不好的文章是要误人子弟的,这就是我批评某个文章的原因。至于把里面那个synchronized单独写成个函数,我可以很负责任的说,效果是完全一样的。monitor也肯定是同一个。关于楼主的“上面这个类的锁是在何处释放的”这个问题,通俗的说,对于同一个对象的lock是在最外层的synchronized离开时被释放的。对象lock的机制:
一个对象的lock至少存在两个参数。一个是owner,在java的synchronized里就是当前线程。另外一个参数是lock_count,初始为0。.尝试进入monitor时(进入synchronized),
1、如果该对象没有被lock,则立即获得这个对象的lock,同时owner设置为当前线程,lock_count=1
2、该对象有lock,owner是当前线程,lock_count加1,不等待。
3、该对象有lock,owner不是当前线程,等待,直到该对象lock被释放,然后到第一步当离开monitor时(离开synchronized)
lock_count减1,如果lock_count大于0,离开,不释放lock;如果lock_count等于0,释放lock
1.“对于一个32位以上的cpu,一个4个byte的object的reference的付值是不会被线程切换中断的。”这并不能解释我的问题,helper的reference已经不是null,可是Helper的class初始化并未完成,你只是说对jvm机制了解的不清楚,那么清楚的了解应该是怎么样的呢?我还是觉得当第二个线程进来看到helper的reference已经不是null,那么就返回了,这样拿到的helper是不可用的,有可能的情况是Helper的fields还是default value,而不是初始化后的值。2.对于lock的机制,看的不是很明白,真的要请教一下了。“lock_count减1,如果lock_count大于0,离开,不释放lock;如果lock_count等于0,释放lock”假设一个线程得到了lock,lock_count=1,这是又一个线程进来,lock_count是不是要加1变成2呢?如果是,当第一个线程要离开时,lock_count减1变成1,它就不释放lock,那么其它线程怎么进来呢如果不是,那么lock_count就还是1,又哪来的“lock_count减1,如果lock_count大于0”呢3.public Helper newHelper{
synchronized(this);
{
return new Helper();
}
}
这个不是相当与public synchronized Helper newHelper(){return new Helper();}吗?
为什么跟getHelper方法里面的monitor是同一个呢?不是很明白.....4.我也不知道你看到的文章是哪一篇,不知道它是如何解释的,所以无从评论啦,或者你可以贴上来大家看看。
new Helper
dup
invokespecial Helper.<init>
astore h
...
aload h
astore helper前面是new Helper,最后的astore h 和 astore helper是付值给h和helper。即使你看不懂jvm指令,也可以大概分析出执行顺序的。也就是说,付值给helper之前,是绝对保证new Helper()已经完成的,否则连单线程都无法保证正确执行。
另外,在java里,除非你直接用ClassLoad.loadClass,其他任何方式使用到class都是初始化完成之后才可以用的。尤其是new Helper(),不能想象Helper.class未初始化完成,就能创建Helper对象。另外,即使在java里直接用Helper.class这样的代码,在jvm里也是被翻译成Class.forName("Helper")的,使用之前是完全保证类初始化完成的。
如果一个线程得到了lock,在它没有被释放之前,第二个线程只能等待。我在上面的(3)里面已经说明了这种情况。另外,这里还有个lock的等待队列的概念,而不用是lock_count来标记的。lock_count加1是指已经得到了lock的线程再一次进入这个对象的monitor的时候。关于lock_count是做什么用的,我用一个例子说明一下:
synchronized(this)
{
...
synchronized(this)
{
...
}
}
这段代码在jvm里的指令如下(为了更容易理解,把jvm里的相关指令结合了一下)//lock的初始值:owner=null;lock_count=0;
monitorenter(this) //进入前的条件 : while(owner != null && owner != currentThread) wait();
//进入后:owner=currentThread; lock_count++; (此时lock_count==1)
...
monitorenter(this) //已经满足前面那个进入前的条件(owner==currentThread),直接进入
lock_count++;//lock_count==2
...
monitorexit(this)
//离开monitor: lock_count--; (此时lock_count==1)
if(lock_count == 0)释放lock; (还不满足lock_count==0的条件,不释放lock)monitorexit(this)
//离开monitor: lock_count--; (此时lock_count==0)
if(lock_count==0)
owner = null;//释放lock (此时满足释放条件,释放lock,同时通知下一个等待进入这个monitor的其他线程)下面是一段用java模拟lock的例子:
class Lock
{
Object owner = null;
int lock_count = 0;
synchronized void lock() throws InterruptedException
{
while(owner != null && owner != Thread.currentThread())
{
this.wait();
}
owner = Thread.currentThread();
lock_count ++;
}
synchronized void unlock()
{
assert owner == Thread.currentThread();
if (owner != Thread.currentThread())
throw new Error("current thread not owner of this lock.");
lock_count --;
if (lock_count==0)
{
owner = null;
this.notify();
}
}
}
synchronized(this);
{
return new Helper();
}
}
这个不是相当与public synchronized Helper newHelper(){return new Helper();}吗?
为什么跟getHelper方法里面的monitor是同一个呢?不是很明白.....monitor是针对某个对象的。如果存在object1==object2,那么object1和object2的monitor就是同一个。
public synchronized ...
{
}
和
public ...
{
synchronized(this)
{
...
}
}
是等价的,这个是常识。不过直接写在方法定义上的那个在class里看不到monitor相关的指令,它由jvm自动完成,并不影响两个代码相同的结果。4、关于那篇文章,还是没必要看,不懂的搞不好会更晕。线程相关的学习资料很多,同时也要自己多写一些代码联系。例如:让面那个lock什么时候的问题,写一小段程序都可以测试出来:
public class ThreadTest implements Runnable
{
void log(String msg)
{
System.out.println("[" + new java.util.Date()
+ "]\t[" + Thread.currentThread().getName()
+ "]\t" + msg);
}
void doSomething()
{
try
{
Thread.sleep(2000);//足够的达到效果的时间
}
catch(Throwable _)
{
}
}
public void run()
{
log("尝试第一次进入Monitor.");
synchronized(this)
{
log("第一次成功进入Monitor");
this.doSomething(); //此处可以去掉,可证明可以马上第二次进入Monitor.
log("尝试第二次进入Monitor.");
synchronized(this)
{
log("第二次成功进入Monitor");
this.doSomething();
log("将要离开第二次进入的Monitor");
}
log("已经离开第二次进入的Monitor");
this.doSomething();//此处要有足够的时间,可以看到lock并未释放
log("将要离开第一次进入的Monitor");
}
log("已经离开第一次进入的Monitor");
}
public static void main(String[] args)
{
ThreadTest test = new ThreadTest();
new Thread(test,"线程#1").start();
new Thread(test,"线程#2").start();
}
}
运行结果:
[Wed Sep 28 00:13:47 CST 2005] [线程#1] 尝试第一次进入Monitor.
[Wed Sep 28 00:13:47 CST 2005] [线程#1] 第一次成功进入Monitor
[Wed Sep 28 00:13:47 CST 2005] [线程#2] 尝试第一次进入Monitor.
[Wed Sep 28 00:13:49 CST 2005] [线程#1] 尝试第二次进入Monitor.
[Wed Sep 28 00:13:49 CST 2005] [线程#1] 第二次成功进入Monitor
[Wed Sep 28 00:13:51 CST 2005] [线程#1] 将要离开第二次进入的Monitor
[Wed Sep 28 00:13:51 CST 2005] [线程#1] 已经离开第二次进入的Monitor
[Wed Sep 28 00:13:53 CST 2005] [线程#1] 将要离开第一次进入的Monitor
[Wed Sep 28 00:13:53 CST 2005] [线程#2] 第一次成功进入Monitor
[Wed Sep 28 00:13:53 CST 2005] [线程#1] 已经离开第一次进入的Monitor //这里跟上一条信息几乎是同时发生的,顺序从原理上来讲不可预知。
[Wed Sep 28 00:13:55 CST 2005] [线程#2] 尝试第二次进入Monitor.
[Wed Sep 28 00:13:55 CST 2005] [线程#2] 第二次成功进入Monitor
[Wed Sep 28 00:13:57 CST 2005] [线程#2] 将要离开第二次进入的Monitor
[Wed Sep 28 00:13:57 CST 2005] [线程#2] 已经离开第二次进入的Monitor
[Wed Sep 28 00:13:59 CST 2005] [线程#2] 将要离开第一次进入的Monitor
[Wed Sep 28 00:13:59 CST 2005] [线程#2] 已经离开第一次进入的Monitor
其次,我得承认我犯的一个低级错误,就是两个monitor是不是同一个的问题,想想就知道肯定是啦,hehe.
1.我想这是我和taolei之间最大的分歧吧.我还是认为,在java中,对helper的reference赋一个还没有初始化的Helper是可能的,其实Double Check在java中能不能用这个问题已经被讨论了很久了,有人为了验证就写了一个Class,然后用Symantec JIT去监视它的执行,结果证明了这一点,也就是说对一个reference赋了一个还没初始化的Class object.可以去google一下DoubleCheckTest这个类名,应该可以找到源代码.我以前看过的一些包括Sun公司的资料中的观点都是这样.这可以说是java memory model的一个缺陷,但是我不肯定的一点是java是否做过memory model的改进或者什么时候进行了改进,因为我近期没有去查过这个问题.2.对于同一个线程连续两次去拿一个lock的机制,首先我完全同意taolei的逻辑是完全行的通的,但是就我所知,java却不是这样实现的.首先,大家一眼就看得出,内部的synchronized其实没有什么意义,jvm在看到同一个线程连续第二次获取同一个lock的时候,它的处理其实相当于"当它不存在",jvm会reorder代码的,在执行的时候变成:synchronized(this) {
h = helper;
if (h == null)
synchronized (this) {
h = new Helper();
helper = h;
}
}内部的synchronized虽然被保留了下来,其实效果上等于根本不存在.对于用lock_count来控制的逻辑,我完全同意行得通,但两种方式对jvm来说实现起来哪一种更简单方便呢?尤其是内部syn根本就没什么意义?我觉得如果是我来做jvm,我不会选taolei的方案,hehe.其实,有人在代码层次上实现lock的机制,是开源的代码,可能叫Mutex什么的,不是很记得了,里面用的就是taolei所讲的lock_count方法. 用这个开源代码的api在代码级控制可以做到象用java的api一样的效果.可是,据我所知(hehe,我必须得强调这一点),jvm不是靠lock_count.那么,如果按照我的解释,taolei写的那个测试lock什么时候被释放的例子已经没什么意义了,因为在代码在执行时被reorder了之后,在内部syn结束和外部syn结束之间已经没有动作了.
Object x1,x2
synchronized(x1)
{
synchronized(x2)
{
}
}
x1,x2两个对象是否为同一个,JVM是不可能预先知道的,更不可能会把里面的synchronized reorder省略掉。"this"在jvm运行期也是一个local变量,编号为0,对synchronized而言,跟x1,x2是没有什么区别的。某些观点太一厢情愿了。都是一些自己都没理解的名词,想当然的认为会怎样。不打算再讨论下去了。