在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並不是共享的,不存在爭用。鎖變量必須是爭用(共享)變量鎖才能生效。