首先先提出幾個問題:
1.多線程編程何時會出現線程不安全的問題?
2.如何解決線程不安全的問題?
線程不安全的本質是多線程共享數據,那麼什麼情況下多線程會共享數據?無外乎這麼幾種情況:
(1)多線程訪問單實例中的實例變量
(2)多線程訪問靜態變量
下面將舉例說明,這個例子模擬鐵路售票系統,實現通過四個售票點發售某日某次列車的100張車票,詳見貼所示:http://bbs.misonsoft.com/thread-1225-1-1.html(創建線程兩種方式的比較)。
第一種情況:多線程訪問單實例中的實例變量
打印結果:
- Thread-2 is saling ticket 100
- Thread-1 is saling ticket 99
- Thread-3 is saling ticket 98
- 。。。
- Thread-2 is saling ticket 2
- Thread-0 is saling ticket 1
- Thread-3 is saling ticket 0
- Thread-1 is saling ticket -1
- Thread-2 is saling ticket -2
這個代碼展示了多線程訪問單實例中的實例變量而導致的數據不同步問題。多線程就是4個售票窗口,即new Thread(t),單個實例便是ThreadTest類,共享的數據就是該實例的tickets變量,即爲100張車票。而出現的數據不同步現象有兩種情況:一個是賣出超過100張車票(如打印結果所示售出102張),還有一種情況是同個座位的車票賣出了多次(本例不會出現)。
接下來說明一下造成數據不安全的第二種情況:多線程訪問靜態變量
上述代碼就是多線程訪問靜態變量導致的數據不同步問題,因爲4個線程共享ticket這個靜態數據,所以也會導致賣出超過100張車票。
我們再來舉個多線程共享靜態數據的例子,懶漢式的單例模式並非線程安全的,如下:
運行結果:
- 對象創建了1次
- Singleton@10b30a7
- 對象創建了2次
- Singleton@1a758cb
- 對象創建了3次
- Singleton@1b67f74
- 對象創建了4次
- Singleton@69b332
- 對象創建了5次
- Singleton@173a10f
很明顯,該對象創建了5次,每一個線程都創建了一個對象。
總結一下,當多線程共享數據時,該數據極有可能出現不同步的狀況。接下來就是第二個問題了:如何防止線程不安全的狀況發生。加鎖可以解決這個問題,每個對象和每個類都提供了一把鎖,當多線程中某一個線程持有這把鎖的時候,所有其他線程都必須等待該線程執行完畢釋放鎖之後才能繼續執行。這把鎖是哪個對象提供的或者是哪個類提供的並不重要,關鍵是該鎖必須是唯一的,因爲只有唯一的對象鎖纔是排他的。
因此第一個場景的代碼可以做如下修改:
也可以這樣加鎖:
甚至可以這樣加鎖:
因此只要確保該鎖是唯一的,即可達到同步的效果。
再來看下第二個場景的數據不同步問題如何解決:
這樣加鎖能否解決問題:
很顯然,這樣加鎖並不能達到同步的目的,因爲當前對象並不是唯一的,這裏的當前對象就是每次創建的線程對象,這裏有4個線程對象,每個對象指代的當前對象都是不一樣的,因此加的鎖都是不一樣的鎖,無法達到排他的目的,因此仍然會造成賣出超過100張車票。這個代碼可以定義一個static對象做爲鎖,或者以ThreadTest.class作爲鎖。
最後單例模式如何加鎖留給大家思考。
總結:加鎖是解決線程不安全的方法之一,加鎖的時候必須確保該鎖是唯一的。