天天看點

可見性問題執行個體

這個例子很簡單,建立的線程裡有一個普通變量stop,用來表示是否結束循環裡的自增操作。主線程啟動這個線程後,将該變量置為true,觀察線程

是否列印出finish loop那行,如果存在可見性問題,主線程修改stop值為true,線程v看stop的值應該還是false。

<code>01</code>

<code>class</code> <code>visibilitythread</code><code>extends</code> <code>thread {</code>

<code>02</code>

<code>    </code><code>private</code> <code>boolean</code> <code>stop;</code>

<code>03</code>

<code>04</code>

<code>    </code><code>public</code> <code>void</code> <code>run() {</code>

<code>05</code>

<code>        </code><code>int</code> <code>i =</code><code>0</code><code>;</code>

<code>06</code>

<code>        </code><code>system.out.println(</code><code>"start loop."</code><code>);</code>

<code>07</code>

<code>        </code><code>while</code><code>(!getstop()) {</code>

<code>08</code>

<code>            </code><code>i++;</code>

<code>09</code>

<code>        </code><code>}</code>

<code>10</code>

<code>        </code><code>system.out.println(</code><code>"finish loop,i="</code> <code>+ i);</code>

<code>11</code>

<code>    </code><code>}</code>

<code>12</code>

<code>13</code>

<code>    </code><code>public</code> <code>void</code> <code>stopit() {</code>

<code>14</code>

<code>        </code><code>stop =</code><code>true</code><code>;</code>

<code>15</code>

<code>16</code>

<code>17</code>

<code>    </code><code>public</code> <code>boolean</code> <code>getstop(){</code>

<code>18</code>

<code>        </code><code>return</code> <code>stop;</code>

<code>19</code>

<code>20</code>

<code>}</code>

<code>21</code>

<code>22</code>

<code>public</code> <code>class</code> <code>visibilitytest {</code>

<code>23</code>

<code>    </code><code>public</code> <code>static</code> <code>void</code> <code>main(string[] args)</code><code>throws</code> <code>exception {</code>

<code>24</code>

<code>        </code><code>visibilitythread v =</code><code>new</code> <code>visibilitythread();</code>

<code>25</code>

<code>        </code><code>v.start();</code>

<code>26</code>

<code>27</code>

<code>        </code><code>thread.sleep(</code><code>1000</code><code>);</code><code>//停頓1秒等待新啟線程執行</code>

<code>28</code>

<code>        </code><code>system.out.println(</code><code>"即将置stop值為true"</code><code>);</code>

<code>29</code>

<code>        </code><code>v.stopit();</code>

<code>30</code>

<code>        </code><code>thread.sleep(</code><code>1000</code><code>);</code>

<code>31</code>

<code>        </code><code>system.out.println(</code><code>"finish main"</code><code>);</code>

<code>32</code>

<code>        </code><code>system.out.println(</code><code>"main中通過getstop擷取的stop值:"</code> <code>+ v.getstop());</code>

<code>33</code>

<code>34</code>

我們先來執行一遍(作業系統:xp,下同。jdk:見圖示):

可見性問題執行個體

執行結果如上圖,有人該問了,線程v最終停下來了,這不是表示它看到stop值為true了嗎?是的,确實如此。但讓我們再看一個這個程式的執行結果。

可見性問題執行個體

這一次,我們發現程式一直未能結束,表示線程v看到stop的值是false,但是主線程列印出的值卻是true。

對比兩次的執行方式,我們發現後一次加上了-server選項。顯示version的時候也由client vm變成了server

vm。那麼client vm與server vm有什麼差別在哪裡?簡單地講,client

vm啟動時做了一般優化,耗時少,啟動快,但程式執行的也相對也較慢;server

vm啟動的時候做了更多優化,耗時多,啟動慢,但程式執行快。如果在運作java指令的時候沒有指定具體模式的時候,會有一個預設值,這個預設值随硬體和

我們再來看個例子,這個例子源于hotspot vm的一個bug:

<code>public</code> <code>class</code> <code>interruptedvisibilitytest {</code>

<code>    </code><code>public</code> <code>void</code> <code>think() {</code>

<code>        </code><code>system.out.println(</code><code>"新線程正在執行"</code><code>);</code>

<code>        </code><code>while</code> <code>(</code><code>true</code><code>) {</code>

<code>            </code><code>if</code> <code>(checkinterruptedstatus())</code><code>break</code><code>;</code>

<code>        </code><code>system.out.println(</code><code>"新線程退出循環"</code><code>);</code>

<code>    </code><code>private</code> <code>boolean</code> <code>checkinterruptedstatus() {</code>

<code>        </code><code>return</code> <code>thread.currentthread().isinterrupted();</code>

<code>        </code><code>final</code> <code>interruptedvisibilitytest test =</code><code>new</code> <code>interruptedvisibilitytest();</code>

<code>        </code><code>thread thinkerthread =</code><code>new</code> <code>thread(</code><code>"thinker"</code><code>) {</code>

<code>            </code><code>public</code> <code>void</code> <code>run() {</code>

<code>                </code><code>test.think();</code>

<code>            </code><code>}</code>

<code>        </code><code>};</code>

<code>        </code><code>thinkerthread.start();</code>

<code>        </code><code>thread.sleep(</code><code>1000</code><code>);</code><code>//等待新線程執行</code>

<code>        </code><code>system.out.println(</code><code>"馬上中斷thinkerthread"</code><code>);</code>

<code>        </code><code>thinkerthread.interrupt();</code>

<code>        </code><code>system.out.println(</code><code>"已經中斷thinkerthread"</code><code>);</code>

<code>        </code><code>thinkerthread.join(</code><code>3000</code><code>);</code>

<code>        </code><code>if</code> <code>(thinkerthread.isalive()) {</code>

<code>            </code><code>system.err.println(</code><code>"thinkerthread未能在中斷後3s停止"</code><code>);</code>

<code>            </code><code>system.err.println(</code><code>"jmv bug"</code><code>);</code>

<code>            </code><code>system.err.println(</code><code>"主線程中檢測thinkerthread的中斷狀态:"</code> <code>+ thinkerthread.isinterrupted());</code>

這個例子也很簡單,thinkerthread一直檢查中斷狀态,主線程在啟動thinkerthread之後的某個時刻調用interrupt中

可見性問題執行個體

thinkerthread沒能退出循環,沒看到主線程所置的中斷狀态。

後面這個例子是hotpost vm的一個bug導緻的,在最新的hotspot中應該已經被修複了(筆者未測試最新版)。其它vm如ibm

j9,jrockit,harmony等并沒有發現這樣的bug。說這是bug,是因為jls中規定了main發出的中斷必須對

thinkerthread可見。但是,如第一個例子,則不是bug,因為jls是允許這種行為的。當在第一個例子的循環中的i++後面加上一句

jls允許未充分同步的代碼出現可見性問題,但是某個實際的jvm完全可以實作的比jls上規定的更強,比如不允許可見性問題出現,那麼,在這樣的

jvm上就展現不出這樣的問題了。第一個例子這裡隻是運作在hotpost下,也許在其它jvm下同樣采用最優化的方式執行,可能并不會出現這裡的問題。

在我們編碼的時候,也許并不知道代碼會跑在什麼樣的系統上,不知道會采用什麼樣的jvm,為了使得寫出的代碼更健壯,我們隻能按照規範所規定的最低

保證去編碼,要避免這類問題,隻有保證代碼充分同步,避免資料争用,而不應該依賴于某個具體jvm實作。即使是具體的某款jvm,不同的版本間也可能存在

着差異。

最後,這樣的例子啟發我們,測試代碼的時候應盡可能啟用各jvm的最佳優化模式。

至此,我們已經了解到實際中多線程運作真的會出現這樣的場景。<b>為什麼會出現可見性問題?有什麼解決方案?</b>下面連結中的内容為我們提供了專業的解答。

<a href="http://ifeve.com/volatile/">http://ifeve.com/volatile/</a>

<a href="http://ifeve.com/java-memory-model-0/">http://ifeve.com/java-memory-model-0/</a>

<a href="http://ifeve.com/java-concurrency-constructs/">http://ifeve.com/java-concurrency-constructs/</a>

<a href="http://bbs.csdn.net/topics/390348019">csdn上有人發的一個真實案例</a>