理解发生在之前和同步

时间:2016-04-03 09:06:04

标签: java multithreading java-memory-model

我正在尝试理解Java happens-before order概念,并且有一些事情看起来很混乱。据我所知,之前发生的只是行动集上的一个订单,并没有提供有关实时执行订单的任何保证。实际上(强调我的):

  

应该注意到之前发生的关系   两次行动之间并不一定意味着必须采取行动   在实现中按顺序放置。 如果重新排序产生   结果与合法执行一致,并非违法。

所以,它说的是,如果有两个动作w(写入)和r(读取),那么 hb(w,r),而不是{ {1}} 可能 实际上在执行r之前发生,但无法保证它会发生。阅读w也会观察到写w

如何确定在运行时随后执行两个操作?例如:

r

操作:

public volatile int v;
public int c;

此处我们有Thread A v = 3; //w Thread B c = v; //r ,但这并不意味着hb(w, r)在分配后会包含值c。如何强制3分配3? synchronization order是否提供此类保证?

4 个答案:

答案 0 :(得分:12)

当JLS说线程A中的某个事件X在线程B中与事件Y的关系之前发生时,它并不意味着X将在Y之前发生。

这意味着 IF X发生在Y之前,然后两个线程都同意 X发生在Y之前。也就是说,两个线程都会看到程序&#39 ; s存储器处于与Y之前发生的X一致的状态。

关于记忆的一切。线程通过共享内存进行通信,但是当系统中有多个CPU时,所有CPU都试图访问同一个内存系统,那么内存系统就成了瓶颈。因此,允许典型的多CPU计算机中的CPU延迟,重新排序和缓存内存操作,以加快速度。

当线程没有彼此交互时,它很有用,但是当它们实际上想要交互时会导致问题:如果线程A将值存储到普通变量中,Java不能保证何时(甚至如果)线程B将看到值更改。

为了在重要时解决这个问题,Java为您提供了某些同步线程的方法。也就是说,让线程就程序的内存状态达成一致。 volatile关键字和synchronized关键字是在线程之间建立同步的两种方法。

我认为他们之所以称之为"之前发生的事情是"是强调关系的传递性:如果你可以证明A发生在B之前,你可以证明B发生在C之前,那么根据JLS中规定的规则,你已经证明A发生在C之前。 / p>

答案 1 :(得分:4)

我想将上述声明与一些示例代码流相关联。

要理解这一点,让我们在下面有两个字段counterisActive的字词。

class StateHolder {
    private int counter = 100;
    private boolean isActive = false;

    public synchronized void resetCounter() {
            counter = 0;
            isActive = true;
    }

    public synchronized void printStateWithLock() {
        System.out.println("Counter : " + counter);
        System.out.println("IsActive : " + isActive);
    }

    public void printStateWithNoLock() {
        System.out.println("Counter : " + counter);
        System.out.println("IsActive : " + isActive);
    }
}

并假设有三个线程T1,T2,T3在StateHolder的同一对象上调用以下方法:

T1同时调用resetCounter()和T2调用printStateWithLock(),T1获得锁定 T3 - >在T1完成执行后调用printStateWithNoLock()

  

应该注意的是,两个动作之间存在的先发生关系并不一定意味着它们必须在实现中以该顺序发生。如果重新排序产生的结果与合法执行一致,则不是非法的。

并且直线说,

根据上述声明,它为JVM,OS或底层硬件提供了灵活性,可以在resetCounter()方法中重新排序语句。当T1执行时,它可以按以下顺序执行语句。

    public synchronized void resetCounter() {
            isActive = true;
            counter = 0;
    }

这与声明一致并不一定意味着它们必须在实施中以该顺序发生。

现在从T2的角度来看,这种重新排序没有任何负面影响,因为T1和T2都在同一个对象上同步,并且T2保证看到两个字段的变化都有变化,无论是否重新排序已经发生或没有,因为之前发生了关系。所以输出总是:

Counter : 0
IsActive : true

这是按照声明,如果重新排序产生的结果与合法执行一致,则不是非法的

但从T3的角度来看,通过重新排序,T3可能会将isActive的更新值视为'真but still see the计数器value as 100`,尽管T1已经完成了它的执行。

Counter : 100
IsActive : true

上述链接中的下一点进一步澄清了该陈述,并说:

  

更具体地说,如果两个动作共享一个发生在之前的关系,那么它们不一定必须按照那个顺序发生在它们不与之共享的任何代码中。例如,在另一个线程中读取的数据争用中的一个线程中的写入可能看起来与这些读取无关。

在此示例中,T3遇到了此问题,因为它与T1或T2之间没有任何先发生关系。这与内联不一定必须按顺序发生在他们不与之前发生关系的任何代码中。

注意:为了简化这种情况,我们有单线程T1修改状态,T2和T3读取状态。

可能有

T1更新counter to 0,稍后再生 在某段时间后,T2会将isActive修改为true并看到counter is 0 打印状态的T3仍然可以看到only isActive as true but counter is 100,尽管T1和T2都已完成执行。

关于最后一个问题:

  

我们有hb(w,r),但这并不意味着c在赋值后将包含值3。如何强制执行c分配3?

public volatile int v;
public int c;

Thread A
v = 3;  //w

Thread B
c = v;  //r

由于vv

,因此c是易变的
  

在对该字段的每次后续读取之前发生对易失性字段(第8.3.1.4节)的写入。

因此可以安全地假设当线程B尝试读取变量var request = require('request'); var mysql = require('mysql'); var async = require ('async'); var connection = mysql.createConnection({ host : 'localhost', user : 'root', password : 'this is secret', database : 'video_db' }); var APIkey = "SECRET KEY"; var apiUrls = []; connection.query('SELECT id FROM videos ORDER id LIMIT 10', function(err, rows, ) { if (err) throw err; rows.forEach(function(row){ var API_URL = 'https://www.googleapis.com/youtube/v3/videos' + '?part=snippet' + '&id=' + row.id + '&key=' + APIkey; apiUrls.push(API_URL); }; }); async.eachSeries(apiUrls, function(apiUrl, next){ request(apiUrl, function (error, response, body) { if(error || response.statusCode != 200) { console.error(response.statusCode, error); return next(); } console.log(JSON.parse(body)); next(); }); }); 时,它将始终读取更新的值,并且var request = require('request'); var mysql = require('mysql'); var async = require ('async'); var connection = mysql.createConnection({ host : 'localhost', user : 'root', password : 'this is secret', database : 'video_db' }); var APIkey = "SECRET KEY"; connection.query('SELECT id FROM videos', function(err, rows) { if (err) throw err; async.eachSeries(rows, function(row, next){ //gets data of a single video var apiUrl = 'https://www.googleapis.com/youtube/v3/videos' + '?part=snippet' + '&id=' + row.id + '&key=' + APIkey; request(apiUrl, function (error, response, body) { if(error || response.statusCode != 200) { console.error(response.statusCode, error); return next(); } console.log(JSON.parse(body)); next(); }); }; }); 将在上面的代码中分配3。

答案 2 :(得分:2)

解释@James的回答:

// Definition: Some variables
private int first = 1;
private int second = 2;
private int third = 3;
private volatile boolean hasValue = false;

// Thread A
first = 5;
second = 6;
third = 7;
hasValue = true;

// Thread B
System.out.println("Flag is set to : " + hasValue);
System.out.println("First: " + first);  // will print 5
System.out.println("Second: " + second); // will print 6
System.out.println("Third: " + third);  // will print 7
  

如果你想要看到的内存(内存和CPU缓存)的状态/值   一个线程写一个变量的语句的时间,

线程A中hasValue=true(写语句)看到的内存状态:

first的值为5,second的值为6,third的值为7

  

从每个后续版本中可以看到(为什么后续,即使只有一个   在这个例子中读取线程B?我们很多人都在使用Thread C   类似于线程B)由另一个读取相同变量的语句   线程,然后标记该变量volatile

如果线程A中的X(hasValue=true)在线程B中 Y(sysout(hasValue))之前发生,则行为应该像在同一线程中的Y之前发生的那样(在X处看到的记忆值应该从Y开始是相同的)

答案 3 :(得分:2)

  

这里我们有hb(w,r),但这并不意味着c在赋值后将包含值3。如何强制执行c分配3?同步订单是否提供了这样的保证?

和你的例子

public volatile int v;
public int c;
Actions:

Thread A
v = 3;  //w

Thread B
c = v;  //r

您的示例中volatile不需要v。我们来看一个类似的例子

int v = 0;
int c = 0;
volatile boolean assigned = false;

操作:

主题A

v = 3;
assigned = true;

主题B

while(!assigned);
c = v;
  1. assigned字段不稳定。
  2. 只有在c = v Thread B assignedtrue对此负责)之后,我们才会在while(!assigned)中发出volatile声明。
  3. 如果我们有happens before - 我们有happens before
  4. assigned == true表示,如果我们看到assigned = true - 我们会在声明v = 3之前看到所有发生的事情:我们会看到assigned == true
  5. 所以当我们有v = 3 - >我们有c = 3
  6. 结果我们有volatile
  7. 没有int v = 0; int c = 0; boolean assigned = false;

    会发生什么
    v = 3;
    assigned = true;
    

    操作:

    主题A

    while(!assigned);
    c = v;
    

    主题B

    assigned

    我们现在volatile没有c

    在这种情况下,Thread Bc == 3的值可以等于0或3。所以没有任何保证   getOffer(id) { this.$http.get('http://127.0.0.1:8000/api/offers/'+id) .then(response => response.json()) .then(result => this.offer = result) } },