原始java原始整数(int)是什么原因?有两个线程共享一个int的实验似乎表明它们是,但当然没有证据证明它们不并不意味着它们是。
具体来说,我跑的测试是这样的:
public class IntSafeChecker {
static int thing;
static boolean keepWatching = true;
// Watcher just looks for monotonically increasing values
static class Watcher extends Thread {
public void run() {
boolean hasBefore = false;
int thingBefore = 0;
while( keepWatching ) {
// observe the shared int
int thingNow = thing;
// fake the 1st value to keep test happy
if( hasBefore == false ) {
thingBefore = thingNow;
hasBefore = true;
}
// check for decreases (due to partially written values)
if( thingNow < thingBefore ) {
System.err.println("MAJOR TROUBLE!");
}
thingBefore = thingNow;
}
}
}
// Modifier just counts the shared int up to 1 billion
static class Modifier extends Thread {
public void run() {
int what = 0;
for(int i = 0; i < 1000000000; ++i) {
what += 1;
thing = what;
}
// kill the watcher when done
keepWatching = false;
}
}
public static void main(String[] args) {
Modifier m = new Modifier();
Watcher w = new Watcher();
m.start();
w.start();
}
}
(仅在32位Windows PC上尝试使用java jre 1.6.0_07)
实质上,Modifier将计数序列写入共享整数,而Watcher检查观察值是否永远不会减少。在必须以四个独立字节(或甚至两个16位字)访问32位值的机器上,观察者有可能在不一致的半更新状态下捕获共享整数,并检测到值减少而不是增加。无论(假设的)数据字节是收集/写入LSB 1st还是MSB 1st,这都应该有效,但最多只能是概率。
考虑到今天的宽数据路径,即使java规范不需要它,32位值也可能是有效的原子,这似乎是非常可能的。事实上,使用32位数据总线,您可能需要更加努力地获得字节的原子访问权限而不是32位数据。
谷歌搜索“java原始线程安全”会在线程安全的类和对象上显示大量内容,但是查找基元的信息似乎是在大海捞针中寻找谚语。
答案 0 :(得分:59)
默认情况下,Java中的所有内存访问都是原子的,long
和double
除外(可能是原子的,但不一定是)。说实话并不清楚非常,但我相信这就是含义。
来自JLS的section 17.4.3:
顺序一致 执行,总订单结束 所有个人行为(如阅读 和写道)这是一致的 程序的顺序,以及每个程序 个人行为是原子的,是 每个线程立即可见。
然后在17.7:
某些实现可能会找到它 方便划分单个写入 动作为64位长或双 值为两个写操作 相邻的32位值。对于 效率的缘故,这种行为是 具体实施; Java虚拟 机器可以自由执行写入 原子或长的双重值 分两部分。
请注意,原子性与波动性有很大不同。
当一个线程将整数更新为5时,保证另一个线程不会看到1或4或任何其他中间状态,但没有任何明显的波动或锁定,另一个线程可以永远看到0。
关于努力获得字节的原子访问权,你是对的:VM可能必须努力......但它确实必须这样做。来自规范的section 17.6:
有些处理器没有提供 能够写入单个字节。它 实现字节是非法的 这种处理器上的数组更新 只是阅读整个单词, 更新适当的字节,和 然后将整个单词写回 记忆。有时这个问题 被称为单词撕裂,并且 处理器无法轻松更新 单个字节隔离其他一些 方法是必需的。
换句话说,由JVM决定是否正确。
答案 1 :(得分:27)
某些实现可能会发现将64位长或双值上的单个写操作分成相邻32位值的两个写操作很方便。
并进一步向下
出于Java编程语言内存模型的目的,对非易失性long或double值的单次写入被视为两个单独的写入:每个32位半写一个。
这似乎意味着对int的写入是原子的。
答案 2 :(得分:4)
我同意Jon Skeet,我想补充一点,很多人混淆了原子性,波动性和线程安全的概念,因为有时这些术语可以互换使用。
例如,考虑一下:
private static int i = 0;
public void increment() { i++; }
虽然有人可能认为这种操作是原子的,但提到的假设是错误的
声明i++;
执行三项操作:
1)阅读
2)更新
3)写上
因此,对此变量进行操作的线程应该像这样同步:
private static int i = 0;
private static final Object LOCK = new Object();
public void increment() {
synchronized(LOCK) {
i++;
}
}
或者这个:
private static int i = 0;
public static synchronized void increment() {
i++;
}
请注意,对于单个对象实例,调用多个线程正在访问的方法并对共享的可变数据进行操作时,必须考虑到方法的参数,局部变量和返回值是本地的每个线程。
有关更多信息,请查看此链接:
http://www.javamex.com/tutorials/synchronization_volatile.shtml
希望这有帮助。
UPDATE :还有一种情况是您可以在类对象本身上进行同步。更多信息:How to synchronize a static variable among threads running different instances of a class in java?
答案 3 :(得分:4)
我认为它不像你期望的那样有效:
private static int i = 0;
public void increment() {
synchronized (i) {
i++;
}
}
integer是不可变的,因此您始终在不同的对象上进行同步。 int“i”被自动装箱到Integer对象,然后你设置锁定它。 如果另一个线程进入此方法,则int被自动装箱到另一个Integer对象,然后在之前设置对另一个对象的锁定。
答案 4 :(得分:3)
从整数或任何较小类型读取或写入应该是原子的,但正如Robert所说,long和double可能会或可能不会取决于实现。但是,任何同时使用读取和写入的操作(包括所有增量操作符)都不是原子操作。因此,如果你必须在一个整数i = 0上运行的线程,一个是i ++,另一个是i = 10,结果可能是1,10或11。
对于这样的操作,您应该查看AtomicInteger,它具有在检索旧值时原子修改值的方法或以原子方式递增值。
最后,线程可以缓存变量的值,并且不会看到从其他线程对其进行的更改。要确保两个线程始终看到另一个线程所做的更改,您需要将变量标记为volatile。
答案 5 :(得分:1)
这不是原子的:
i++;
但是,这是:
i = 5;
我认为这是一些混乱的地方。
答案 6 :(得分:0)
这有点复杂,与系统字大小有关。 Bruce Eckel更详细地讨论了它:Java Threads。
答案 7 :(得分:0)
原子读写只是意味着你永远不会读,例如int更新的前16位和旧值的另一位。
这没有说明其他线程何时看到这些写道。
长话短说,当两条线程在没有记忆障碍的情况下竞争时,会丢失一些东西。
旋转两个或多个增加单个共享整数的线程,并计算自己的增量。当整数达到某个值时(例如,INT_MAX。为了让事情变暖,很好和很大)停止一切并返回int的值和每个线程执行的增量数。
import java.util.Stack;
public class Test{
static int ctr = Integer.MIN_VALUE;
final static int THREADS = 4;
private static void runone(){
ctr = 0;
Stack<Thread> threads = new Stack<>();
for(int i = 0; i < THREADS; i++){
Thread t = new Thread(new Runnable(){
long cycles = 0;
@Override
public void run(){
while(ctr != Integer.MAX_VALUE){
ctr++;
cycles++;
}
System.out.println("Cycles: " + cycles + ", ctr: " + ctr);
}
});
t.start();
threads.push(t);
}
while(!threads.isEmpty())
try{
threads.pop().join();
}catch(InterruptedException e){
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println();
}
public static void main(String args[]){
System.out.println("Int Range: " + ((long) Integer.MAX_VALUE - (long) Integer.MIN_VALUE));
System.out.println(" Int Max: " + Integer.MAX_VALUE);
System.out.println();
for(;;)
runone();
}
}
这是我的四核盒子上的测试结果(可以随意使用代码中的线程数,我只是匹配我的核心数,显然):
Int Range: 4294967295
Int Max: 2147483647
Cycles: 2145700893, ctr: 76261202
Cycles: 2147479716, ctr: 1825148133
Cycles: 2146138184, ctr: 1078605849
Cycles: 2147282173, ctr: 2147483647
Cycles: 2147421893, ctr: 127333260
Cycles: 2146759053, ctr: 220350845
Cycles: 2146742845, ctr: 450438551
Cycles: 2146537691, ctr: 2147483647
Cycles: 2110149932, ctr: 696604594
Cycles: 2146769437, ctr: 2147483647
Cycles: 2147095646, ctr: 2147483647
Cycles: 2147483647, ctr: 2147483647
Cycles: 2147483647, ctr: 330141890
Cycles: 2145029662, ctr: 2147483647
Cycles: 2143136845, ctr: 2147483647
Cycles: 2147007903, ctr: 2147483647
Cycles: 2147483647, ctr: 197621458
Cycles: 2076982910, ctr: 2147483647
Cycles: 2125642094, ctr: 2147483647
Cycles: 2125321197, ctr: 2147483647
Cycles: 2132759837, ctr: 330963474
Cycles: 2102475117, ctr: 2147483647
Cycles: 2147390638, ctr: 2147483647
Cycles: 2147483647, ctr: 2147483647
答案 8 :(得分:0)
在线程之间共享数据时,需要同步。当处理整数(可以从主内存到多处理器系统中的处理器缓存)时,线程可能正在更新绑定到特定处理器的整数的本地副本。
Java中的易失性(See Wiki in Java Section)关键字将确保对Integer的任何更新都将在内存中发生,而不是在本地副本中发生。
此外,要将更新同步到Integer,请考虑使用AtomicInteger。此实现具有方法(compareAndSet)来检查某个值是否是线程期望的值,并设置该值。如果不匹配,则另一个线程可能已经更新了该值。 AtomicInteger将以原子操作执行对Integer的读取和更新,其优点是不必阻塞。