Java非執行緒安全問題的解決方法

2020-07-16 10:04:37
在上節《多執行緒之間存取範例變數》中出現了一個術語——非執行緒安全非執行緒安全主要是指多個執行緒對同一個物件中的同一個範例變數進行操作時會出現值被更改、值不同步的情況,進而影響程式的執行流程。下面用一個範例來學習一下如何解決非執行緒安全問題。

本案例模擬了多執行緒下的使用者登入驗證功能。首先編寫一個類實現驗證功能, LoginCheck 類的程式碼如下:
package ch14;
public class LoginCheck
{
    private static String username; 
    private static String password; 
    public static void doPost(String _username,String _password)
    { 
        try
        { 
            username=_username; 
            if (username.equals("admin"))
            { 
                Thread.sleep(5000); 
            } 
            password=_password; 
            System.out.println("username="+username+"password="+password); 
        }
        catch(InterruptedException e)
        { 
            // TODO Auto-generated catch block 
            e.printStackTrace(); 
        } 
    } 
}

接下來建立執行緒類 LoginThreadA 和 LoginThreadB,這兩個執行緒都呼叫 LoginCheck 類進行登入資訊。其中 LoginThreadA 類的程式碼如下:
package ch14;
public class LoginThreadA extends Thread
{
    public void run()
    { 
        LoginCheck.doPost("admin","admin"); 
    }
}

LoginThreadB 類的程式碼如下:
package ch14;
public class LoginThreadB extends Thread
{ 
    public void run()
    { 
        LoginCheck.doPost("root","root"); 
    }
}

現在編寫主執行緒程式,分別建立 LoginThreadA 執行緒範例和 LoginThreadB 執行緒實現,然後啟動這兩個執行緒。主執行緒的程式碼如下:
package ch14;
public class Test07
{
    public static void main(String[] args)
    {
        LoginThreadA a=new LoginThreadA();
        a.run();    //啟動執行緒LoginThreadA
        LoginThreadB b=new LoginThreadB();
        b.run();    //啟動執行緒LoginThreadB
    }
}

程式執行後的結果如下所示:
username=root password=admin
username=root password=root

從執行結果中可以看到使用者名稱 root 出現了這兩次,這是由於多個執行緒同時修改 username,導致值不一致的情況。

仔細檢視程式碼可以發現問題出現在兩個執行緒都會呼叫 doPost() 方法上。解決這個非執行緒安全問題的方法是使用 synchronized 關鍵字修飾 doPost() 方法,即不允許多個執行緒同時修改 doPost() 方法中的變數。更改程式碼如下:
package ch14;
public class LoginCheck
{
    private static String username; 
    private static String password; 
    synchronized public static void doPost(String _username,String _password)
    { 
        try
        { 
            username=_username; 
            if (username.equals("admin"))
            { 
                Thread.sleep(5000); 
            } 
            password=_password; 
            System.out.println("username="+username+"password="+password); 
        }
        catch (InterruptedException e)
        { 
            // TODO Auto-generated catch block 
            e.printStackTrace(); 
        } 
    } 
}

再次執行主執行緒,此時將看到如下所示的結果,說明不存在“非執行緒安全”問題了。
username=admin password=admin
username=root password=root