程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 關於Java語言中的線程安全問題

關於Java語言中的線程安全問題

編輯:關於JAVA

Java語言是一種支持多線程的語言,它通過同步(互斥)和協作(等待和喚醒)來完成。這裡聊聊同步。

線程不安全主要來自於類變量(靜態變量)和實例變量,前者位於方法區中,後者位於堆中,都是共享區域。局部變量是沒有這個問題的,因為它在線程獨有的棧中。先看下面的例子:

  1. public class Test implements Runnable {
  2. private int j;
  3. public Test() {
  4. }
  5. public void testThreadLocal() {
  6. System.out.println(Thread.currentThread().getId()
  7. + ":============================= begin");
  8. j = 2;
  9. System.out.println(Thread.currentThread().getId() + ":" + j);
  10. j = 20;
  11. System.out.println(":" + j * 3 + ":");
  12. System.out.println(Thread.currentThread().getId()
  13. + ":============================= end");
  14. }
  15. public static void main(String[] args) {
  16. Test t = new Test();
  17. for (int i = 0; i < 3000; i++) {
  18. new Thread(t).start();
  19. }
  20. }
  21. @Override
  22. public void run() {
  23. testThreadLocal();
  24. }
  25. }

執行這個類的main方法,會出現線程不安全的問題。上面藍色的語句,應該打印出:60:,但實際開了3000個線程(為了方便出現不安全的現象)後,會出現下面紅色的:6:

655:============================= end

49:============================= end

:6:

156:============================= end

152:2

:60:

修改main方法,用多個Test對象,結果也是一樣。

  1. public static void main(String[] args) {
  2. Test t = new Test();
  3. for (int i = 0; i < 3000; i++) {
  4. new Thread(new Test() ).start();
  5. }
  6. }

我們保留多個Test對象的做法,在testThreadLocal方法上加一個同步關鍵字。

  1. public synchronized void testThreadLocal()

結果沒有用,仍然是不安全的。改成一個Test對象,這下可以了。原因很簡單,synchronized通過在對象上加鎖來實現線程安全。當使用多個Test對象時,僅僅在this對象上加鎖是不行的,要在類(在Java中,類仍然通過一個特殊的Class對象來體現)上加鎖才行。所以改成:

  1. public void testThreadLocal() {
  2. synchronized (this.getClass()) {
  3. System.out.println(Thread.currentThread().getId()
  4. + ":============================= begin");
  5. j = 2;
  6. System.out.println(Thread.currentThread().getId() + ":" + j);
  7. j = 20;
  8. System.out.println(":" + j * 3 + ":");
  9. System.out.println(Thread.currentThread().getId()
  10. + ":============================= end");
  11. }
  12. }

這下可以了。我們再看使用類變量的情況,先把synchronized關鍵字去掉,恢復到最初的代碼,然後把實例變量改成類變量。

  1. private int j;
  2. private static int j;

實驗結果和使用實例變量基本相同,唯一的不同之處在於,我們可以這樣在類上加鎖了,注意,testThreadLocal方法被改成靜態方法。

  1. public synchronized static void testThreadLocal() {
  2. System.out.println(Thread.currentThread().getId()
  3. + ":============================= begin");
  4. j = 2;
  5. System.out.println(Thread.currentThread().getId() + ":" + j);
  6. j = 20;
  7. System.out.println(":" + j * 3 + ":");
  8. System.out.println(Thread.currentThread().getId()
  9. + ":============================= end");
  10. }

從上面的例子看到,我們使用類變量和實例變量的時候,都要非常小心,在多線程的環境下,很容易出現線程不安全的情況。上面我們還僅僅以基本類型int為例,如果是其他復雜類型,甚至像long這種在賦值時要兩次原子操作的基本數據類型,線程不安全的情況還要隱秘一些。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved