内存不一致与线程交错有何不同?

时间:2015-10-25 00:43:14

标签: java multithreading memory volatile

我正在编写一个多线程程序,正在研究是否应该使用var TableData:Array< String > = Array < String >() override func viewDidLoad() { super.viewDidLoad() splitViewController!.preferredDisplayMode = UISplitViewControllerDisplayMode.AllVisible UINavigationBar.appearance().barTintColor = UIColor(red: 52.0/255.0, green: 170.0/255.0, blue: 220.0/255.0, alpha: 1.0) UINavigationBar.appearance().tintColor = UIColor.whiteColor() UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName : UIColor.whiteColor()] //JSON let url = NSURL(string:"https://www.kimonolabs.com/api/7flcy3qm?apikey=gNq3hB1j0NtBdAvXJLEFx8JaqtDG8y6Y")! let session = NSURLSession.sharedSession() let task = session.dataTaskWithURL(url) { (data, response, error) -> Void in if error != nil { print(error) } else { if let _ = data { do { let jsonString = try NSString.init(contentsOfURL: url, encoding: NSUTF8StringEncoding) // Create JSON object from data let json = JSON(data: jsonString.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!) // Check if array for key "collection2" exists if let collection2 = json["results"]["collection2"].array { // Create JSON array from it and loop for each object for (_, subJson):(String, JSON) in JSON(collection2) { // Check if dictionary for key "Event" exists if let event = subJson["Event"].dictionary { print(event) } // Check if string for key "Hasta" exists if let hasta = subJson["Hasta"].string { print(hasta) } // Check if string for key "Location" exists if let location = subJson["Location"].string { print(location) } } } } catch { print("In catch block") } } } } task.resume() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } // MARK: - Table view data source override func numberOfSectionsInTableView(tableView: UITableView) -> Int { // #warning Incomplete implementation, return the number of sections return 1 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { // #warning Incomplete implementation, return the number of rows return self.TableData.count } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) cell.textLabel?.text = self.TableData[indexPath.row] return cell } } 作为我的布尔标志。关于并发性的文档oracle跟踪并未解释除{:1}之外的任何内容:

  

不同线程有内存一致性错误   不一致的观点应该是什么相同的数据。

假设这些不一致的视图仅在“写入”操作之后发生是有意义的。但是多久之后呢?

示例1

volatile

由于memory consistency errorsThread A: Retrieve flag. Thread B: Retrieve flag. Thread A: Negate retrieved value; result is true. Thread A: Store result in flag; flag is now true. Thread B: System.out.print(flag) --> false 同时运行,因此打印也可能为true,具体取决于检索时间Thread A。对于不一致而言,这是完全合理的。

但是描述Thread B的方式(写入变量不一定反映在其他线程中)听起来也是如此:

示例2

flag

我强烈认为示例2不可能是真的。问题是,只有当 为真时才能看到使用memory consistency errors来建立Thread A: Retrieve flag. Thread A: Change retrieved value; result is true. Thread A: Store result in flag; flag is now true. //any longer amount of time passes (while Thread A didn't explicitly happen-before Thread B, it obviously did.) Thread B: Retrieve flag. Thread B: System.out.print(flag) --> true OR false (unpredictable)

如果是真的,为什么会这样呢?如果不是......为什么要使用volatile

1 个答案:

答案 0 :(得分:5)

了解JVM内存模型最具挑战性的事情之一是,严格来说, timing (即你的挂钟)完全无关紧要。

无论多久(根据您的挂钟)时间在2个独立线程中的两次操作之间经过,如果发生在关系之前,绝对不能保证每个线程在内存中会看到什么。

在您的示例2 中,您提到了一个棘手的部分,

  

虽然线程A没有明确发生 - 在线程B之前,显然已经

从上面的描述来看,唯一可以说是显而易见的是,根据您的挂钟测量的时间,某些操作发生以后比其他人。但 并不意味着严格意义上的JVM内存模型中的 关系。

让我展示一组与您上面的示例2的描述兼容的操作(即,根据您的挂钟所做的测量),这可能会导致 true false ,不能保证。

  • 主线程M启动线程A和线程B :线程M和线程A之间的 发生在关系之前 在线程M和线程B之间。因此,如果没有其他事情发生,线程A和线程B将看到与该布尔值的线程M相同的值。我们假设它已初始化为false(以使其与您的说明兼容)。

假设您在多核计算机上运行。此外,假设线程A在Core 1中分配,而线程B在Core 2中分配。

  • 线程A读取布尔值:它必须读取false(参见上一个要点)。当读取发生时,可能发生某些内存页面(包括包含该布尔值的内存页面)将被缓存到Core 1的L1缓存或L2缓存中 - 任何本地缓存具体核心。

  • 线程A否定并存储布尔值:它现在将存储true。但问题是:在哪里?在发生之前 - 发生之前,线程A可以自由地将此新值存储在运行该线程的Core的本地缓存中。因此,该值可能会在Core 1的L1 / L2高速缓存上更新,但在处理器的L3高速缓存或RAM中将保持不变。

  • 经过一段时间(根据您的挂钟),线程B读取布尔值:如果线程A没有将更改刷新到L3或RAM,线程B读取false完全有可能。另一方面,如果线程A刷新了更改,则可能线程B将读取true(但仍然无法保证 - 线程B可能已经收到了线程的副本M对存储器的看法,由于之前没有发生,它不会再次进入RAM并仍然会看到原始值。

保证任何事情的唯一方法是在之前发生 :它会强制线程A刷新其内存,并且会强制线程B不是从本地缓存中读取,而是真正从“权威”中读取它。源。

如果没有事先发生,正如您从上面的示例中看到的那样,任何事情都可能发生,无论在不同线程中的事件之间经过了多少时间(从您的角度来看)。

现在,一个重要问题:为什么volatile会解决示例2中的问题

如果该布尔变量标记为volatile,并且根据上面的示例2(即从挂钟的角度来看)进行交错操作,那么,只有这样,线程B才能保证看到true(即,根本没有任何保证)。

原因是volatile有助于确定之前的关系。它如下:volatile变量的写入发生 - 在对同一变量的任何后续读取之前。

因此,通过标记变量volatile,如果从时序角度看,线程B仅在线程A更新后才读取,则线程B保证看到更新(从内存一致性的角度来看)。

现在有一个非常有趣的事实:如果线程A对非易失性变量进行了更改,那么更新一个volatile变量,然后(从挂钟的角度来看)线程B读取该volatile变量,它也保证线程B将看到非易失性变量的所有更改!这是由非常复杂的代码使用,它们希望避免锁定并且仍然需要强大的内存一致性语义。它通常被称为易失性变量捎带

作为最后的评论,如果你试图模拟(缺乏)发生在之前的关系,它可能会令人沮丧......当你把东西写到控制台时(即{{ 1}}),JVM可能会在多个不同的线程之间进行大量的同步,因此很多内存实际上可能会被刷新,并且你不一定能够看到你正在寻找的效果......它和#39;很难模拟这一切!