返回信息流今天碰到一个问题,大致伪码如下:
class test
{
private static boolean a = false;
public static void init()
{
try
{
a = true;
...
}
catch (Exception e)
{
system.out.println(e.getMessage());
}
finally
{
a = false;
}
}
public hashtable get()
{
while(a)
{}
....
}
}
有多个线程同时在调用get方法,而每天某个时间点会有一个线程调用一次init方法
正常来讲,init方法运行时,所有调用get方法的线程都会挂起,直到init方法运行结束。本机模拟测试也是这个结果。
但是今天在init方法运行时,有线程调用get方法出现挂起无法继续执行的情况,而且确认init方法正确执行完毕了,后续调用get方法的线程也都能正常执行。。。
有50几天服务器在跑这段代码,但只有三台服务器出现这种情况,有一台服务器还出现cpu高的情况。。。
哪位大牛能帮忙答疑解惑一下?多谢了
这是一条镜像帖。来源:北邮人论坛 / java / #35278同步于 2014/10/15
该镜像源已超过 30 天没有更新,可能在源站已被删除。
Java机器人发帖
轮询挂起的问题
uriel
2014/10/15镜像同步9 回复
订阅后,新回复会通过你的通知中心匿名送达。
9 条回复
欢迎进坑。简单地说,多线程对共享变量的存取必须进行同步,使用volatile、锁、原子类型都是办法。请看Oracle的官方教程,我认为是最简明的: http://docs.oracle.com/javase/tutorial/essential/concurrency/
尤其是memory consistency error这一节: http://docs.oracle.com/javase/tutorial/essential/concurrency/memconsist.html
a加volatile,多个线程会出现看到的值是不一样的情况,主要是寄存器和内存之间的数据同步有延迟。
a需要用Atomic,原因是你认为a=true是原子,但是不太是。
另外,如果想阻塞get,可以用synchronized或者concurrent包下的lock来做,而不是while(true){}会占用CPU的,只不过机器多核的不太显你的应用有问题,放到实际生产环境,这种程序会使机器资源占用率高和QPS受影响。
【 在 nuanyangyang 的大作中提到: 】
: 欢迎进坑。简单地说,多线程对共享变量的存取必须进行同步,使用volatile、锁、原子类型都是办法。请看Oracle的官方教程,我认为是最简明的: http://docs.oracle.com/javase/tutorial/essential/concurrency/
: 尤其是memory consistency error这一节: http://docs.oracle.com/javase/tutorial/essential/concurrency/memconsist.html
a用的boolean型,是原子类型啊。两个教程倒是都能理解,但是还是不能解释为什么while判断被挂起了,正常来讲即使while判断的线程和init的线程不同步,但最终a的值都会还原为false,那么最终while判断都会跳出啊?
【 在 neo861002 的大作中提到: 】
: a加volatile,多个线程会出现看到的值是不一样的情况,主要是寄存器和内存之间的数据同步有延迟。
: a需要用Atomic,原因是你认为a=true是原子,但是不太是。
: 另外,如果想阻塞get,可以用synchronized或者concurrent包下的lock来做,而不是while(true){}会占用CPU的,只不过机器多核的不太显你的应用有问题,放到实际生产环境,这种程序会使机器资源占用率高和QPS受影响。
点赞,这个解释的通,没有考虑到线程会有自己的副本内存,使用volatile能保证读安全。那么再延伸一下,java这个主内存和副本内存是什么级别的?是jvm将内存进行划分还是寄存器与内存这个级别的?高手能否给指点一下?
【 在 uriel 的大作中提到: 】
:
: 点赞,这个解释的通,没有考虑到线程会有自己的副本内存,使用volatile能保证读安全。那么再延伸一下,java这个主内存和副本内存是什么级别的?是jvm将内存进行划分还是寄存器与内存这个级别的?高手能否给指点一下?
自食其力丰衣足食~~~看来对jvm和jmm还需要研究一下
线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个私有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。它涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化。
一看你说boolean是原子类型就知道你没理解。重看。
【 在 uriel 的大作中提到: 】
:
: a用的boolean型,是原子类型啊。两个教程倒是都能理解,但是还是不能解释为什么while判断被挂起了,正常来讲即使while判断的线程和init的线程不同步,但最终a的值都会还原为false,那么最终while判断都会跳出啊?
来自「北邮人论坛手机版」
嗯,就看了第二个文档,感觉说的都是基础的,也可能是因为我英文实在不好,所以没关注到重点ˊ_>ˋ
不过这个问题了解原因了
只是对你说的boolean不是原子类型比较在意,能否帮忙给解释一下?还是我们说的原子类型不是一个?
【 在 nuanyangyang 的大作中提到: 】
一看你说boolean是原子类型就知道你没理解。重看。...
发现歧义了。“原子”可以有两种理解:
1.它是基本的类型(primitive type),和复合(composite type)类型是有区别的
2.对该类型的内存空间的读写总是原子的(atomic),也就是每次读操作,一定看到某次写操作写入的整个值,而不是一半值。
我说的原子是后者。
Java里,非volatile的类成员变量不是“原子”的,因为如果两个线程读写这个变量又没有外加同步,那么有可能会看到一半旧值一半新值。比如,有的机器上,写一个64位的整数会分成两次写,每次写32位。所以有可能另一个线程看到低32位是新的而高32位是旧的,甚至一段时间内根本看不到值改变了。
局部变量没有这个问题,因为局部变量只能被一个线程读写。这和C和C++是不同的。
所有volatile的成员变量都是原子的。读写都是原子的,不会看到“写了半个值”的情况。对所有的引用类型的变量的读写也都是原子的。
另一个问题是顺序。有多核处理器的情况下,每个处理器都有缓存,所以对某个内存的地址的改变,并不是每个处理器都能同时看到改变。而且不同的处理器看到的改变顺序是不一样的。
比如,假设有两个普通的全局变量x,y,初始值都是0,有4个线程:
线程1: x = 1
线程2: y = 2
线程3: a1 = x; a2 = y;
线程4: b1 = x; b2 = y;
那么有可能会出现这种情况:a1=1,a2=0,b1=0,b2=2。也就是,在线程3看来,x的值先被读到,y的值还没被读到。但在线程4看来,y先被读到,x却还没到达。其原因,有可能线程1、3所在的CPU共享了L1缓存、线程2、4所在的CPU共享了L1缓存,但1、2、3、4却只共享L3缓存,这样数据在1-3, 2-4之间传递得比1-4, 2-3之间快。于是就出现了上述情况。要避免这种情况出现,需要插入fence。但如果你用Java,用volatile的话编译器会帮你这样做,程序员不用考虑fence。
Java里volatile变量保证一个顺序,叫“sequential consistency”。所有的线程的对所有的volatile变量的读写有一个全局的顺序,就好象是按这个顺序依次完成的。所以,在Java里,如果还是上述的例子,而x和y是volatile变量,那么绝不会出现a1=1,a2=0,b1=0,b2=2的情况,因为所有volatile变量的改变,在所有线程的视角下,看到的改变顺序都是一样的。
Java还有一些原子类型,比如java.util.concurent.atom.AtomicInteger。它不仅有volatile变量那样的原子性,还支持另外一些原子操作,比如“读、改、写”一步完成,不可分割。比如AtomicInteger#getAndIncrement(),可以读,同时将值加1。多线程即使未加同步地调用这个方法,每个线程也会看到一个不同的值。相比起来,下面的程序就很危险:
class BadInteger {
private int value = 0;
public int getAndIncrement() { // 危险!!!
int a = value;
int b = a + 1;
value = b;
// 就算写a = value++也没用。这也不是原子的。
return a;
}
}
多个线程一起执行,有可能两个同时执行到a=value,分别同时读到0,然后两个分别把b存回去,这样value最后等于1,等于整体只加了一次。所以这种原子操作还是要用AtomicInteger。
【 在 uriel 的大作中提到: 】
: 嗯,就看了第二个文档,感觉说的都是基础的,也可能是因为我英文实在不好,所以没关注到重点ˊ_>ˋ
: 不过这个问题了解原因了
: 只是对你说的boolean不是原子类型比较在意,能否帮忙给解释一下?还是我们说的原子类型不是一个?
: ...................
嗯呢,原子性不仅在时间上,也在空间上。之前只理解了时间上的,所以出现了这个帖子说的错误
【 在 nuanyangyang 的大作中提到: 】
: 发现歧义了。“原子”可以有两种理解:
: 1.它是基本的类型(primitive type),和复合(composite type)类型是有区别的
: 2.对该类型的内存空间的读写总是原子的(atomic),也就是每次读操作,一定看到某次写操作写入的整个值,而不是一半值。
: ...................