事實也許和你想的不一樣:volatile,原子類,synchronized在scala的Thread,線程池,Future中使用對比

在scala和java中通常用於數據同步的材料有volatile關鍵字,原子類,synchronized關鍵字。但是在不同的多線程環境下,產生的效果卻並不相同。

實驗

在獨立線程環境下使用volatile關鍵字,原子類,synchronized關鍵字

構造1000個獨立的Thread實例同時啓動

    // 不使用任何同步機制
    var i: Int = 0
    (0 to 10000) foreach (_ => new Thread() {
      i = i + 1
    }.start())
    
    // 使用volatile同步
    @volatile var j: Int = 0
    (0 to 10000) foreach (_ => new Thread() {
      j = j + 1
    }.start())

    // 使用原子類同步
    val k = new AtomicLong(0)
    (0 to 10000) foreach (_ => new Thread() {
      k.incrementAndGet()
    }.start())

    // 使用synchronized同步
    var m: Int = 0
    var lock: String = ""
    (0 to 10000) foreach (_ => new Thread() {
      lock.synchronized {
        m = m + 1
      }
    }.start())

    Thread.sleep(1000)
    println(i, j, k, m) // (10001,10001,10001,10001)

結果表明在獨立線程裏使用三類同步方式都生效了。但是實際上使用volatile沒法進行同步,因爲只能保證可見性,不能保證原子性。所以前兩種都是巧合而已。

在Future環境裏使用volatile關鍵字,原子類,synchronized關鍵字進行同步

構造1000個獨立的Future實例同時啓動

// 不使用任何同步機制
var i: Int = 0
   (0 to 10000) foreach (_ => Future {
     i = i + 1
   })
   
 // 使用volatile同步
 @volatile var j: Int = 0
 (0 to 10000) foreach (_ => Future {
   j = j + 1
 })
 
 // 使用原子類同步
val k = new AtomicLong(0)
(0 to 10000) foreach (_ => Future {
  k.incrementAndGet()
})

// 使用synchronized同步
var m: Int = 0
(0 to 10000) foreach (_ => this.synchronized {
  m = m + 1
})

Thread.sleep(1000)
println(i, j, k, m) // (9754,9892,10001,10001) volatile關鍵字在Future裏無效,synchronized有效,原子類有效

輸出表明:

  • volatile 關鍵字在這裏並沒有生效。說明前面的猜測是正確的
  • 原子類 在Future裏是有效的

線程池裏使用volatile關鍵字,原子類,synchronized關鍵字進行同步

// 不使用任何同步機制
var i = 0
val service1 = Executors.newFixedThreadPool(10)
(0 until 1000) foreach (_ => service1.execute(new Runnable {
  override def run(): Unit = i += 1
}))

// 使用volatile同步
@volatile var j = 0
val service2 = Executors.newFixedThreadPool(10)
(0 until 1000) foreach (_ => service2.execute(new Runnable {
  override def run(): Unit = j += 1
}))

// 使用synchronized同步
var k = 0
val lock: String = ""
val service3 = Executors.newFixedThreadPool(10)
(0 until 1000) foreach (_ => service3.execute(new Runnable {
  override def run(): Unit =
    lock.synchronized {
      k += 1
    }
}))

// 使用原子類同步
val m = new AtomicLong(0)
val service4 = Executors.newFixedThreadPool(10)
(0 until 1000) foreach (_ => service4.execute(new Runnable {
  override def run(): Unit = m.incrementAndGet()
}))


Thread.sleep(1000)
println(i, j, k, m) // (994,989,1000,1000)

輸出表明:
輸出結果和使用Future輸出是一致的。

總結

不同步 voliate 原子類 synchronized
單線程 yes yes yes yes
多個獨立線程 no no yes yes
Future(線程池) no no yes yes
  • 無論什麼情況下,使用synchronized 和 原子類 進行同步總是ok的。但是synchronized使用範圍更廣,原子類只是和單一變量同步。
  • voliate 並不是用來同步,只是用來做標識位。對 voliate 的修改並不能保證原子性。

注意:
我們在三次實驗中使用synchronized的方法個並不一樣。第一次和第三次用了一個空字符串作爲lock.但是第二次用的是this. 因爲,在第一次和第三次中,this並不是共享的,不存在爭用。鎖變量必須是爭用(共享)變量鎖才能生效。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章