解決線程不安全問題


 

更多精彩文章歡迎關注公眾號“Java之康庄大道”

 

 

當多個線程並發訪問同一個資源對象時,可能會出現線程不安全的問題,比如現有100個高鐵座位,現在有請三個窗口(A,B,C)同時售票.,此時使用多線程技術來實現這個案例.

package com.yunqing.ssm.test;

/**
 * 存在線程安全問題
 */
public class TestRunnable {

    public static void main(String[] args) {
        Ticket tick = new Ticket();

        Thread t1 = new Thread(tick,"A窗口");
        Thread t2 = new Thread(tick,"B窗口");
        Thread t3 = new Thread(tick,"C窗口");

        t1.start();
        t2.start();
        t3.start();
    }



}

class Ticket implements Runnable{

    int ticket = 100;

    @Override
    public void run() {

        while (true){
            
            try {
                //模擬網絡延遲
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
                if (ticket>0)
                    System.out.println(Thread.currentThread().getName()+"售票,票號為:"+ticket--);

        }

    }

}

以上代碼運行結果:

 

為什么編號為84的座位號被3個窗口售出了?

當A窗口打印84座位號,還沒打印完的時候,其他兩個線程就也進入到了84號座位票的分配操作中,所以導致線程安全問題。

要解決上述多線程並發訪問多一個資源的安全性問題,就必須得保證打印座位號和座位號總數減1操作,必須同步完成.即是說,A線程進入操作的時候,BC線程只能在外等着,A操作結束,ABC才有機會進入代碼去執行.

解決多線程並發訪問資源的安全問題,有三種方式:

方式1:同步代碼塊

方式2:同步方法

方式3:鎖機制(Lock)

 

 

方式1:同步代碼塊

語法:

synchronized(同步鎖)

{

     需要同步操作的代碼

}

 

同步鎖:

為了保證每個線程都能正常執行原子操作,Java引入了線程同步機制.也稱為同步監聽對象/同步鎖/同步監聽器/互斥鎖。

實際上,對象的同步鎖只是一個概念,可以想象為在對象上標記了一個鎖,誰拿到鎖,誰就可以進入代碼塊,其他線程只能在代碼塊外面等着,而且注意,在任何時候,最多允許一個線程擁有同步鎖.

Java程序運行可以使用任何對象作為同步監聽對象,但是一般的,我們把當前並發訪問的共同資源作為同步監聽對象.

package com.yunqing.ssm.test;

/**
 * 存在線程安全問題
 */
public class TestRunnable {

    public static void main(String[] args) {
        Ticket tick = new Ticket();

        Thread t1 = new Thread(tick,"A窗口");
        Thread t2 = new Thread(tick,"B窗口");
        Thread t3 = new Thread(tick,"C窗口");

        t1.start();
        t2.start();
        t3.start();
    }



}

class Ticket implements Runnable{

    int ticket = 100;

    @Override
    public void run() {

        while (true){

            try {
                //模擬網絡延遲
                Thread.currentThread().sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //this代表Ticket對象,Ticket對象是多線程共享資源
            synchronized (this){
                if (ticket>0)
                    System.out.println(Thread.currentThread().getName()+"售票,票號為:"+ticket--);
            }


        }

    }

}

  方式2:同步方法:

使用synchronized修飾的方法,就叫做同步方法,保證A線程執行該方法的時候,其他線程只能在方法外等着.

Synchronized public void doWork(){

     ///TODO

}

同步鎖是誰:

      對於非static方法,同步鎖就是this.  

      對於static方法,我們使用當前方法所在類的字節碼對象(Ticket.class).

package com.yunqing.ssm.test;

/**
* 存在線程安全問題
*/
public class TestRunnable {

public static void main(String[] args) {
Runnable a = new Ticket();
new Thread(a,"A窗口").start();
new Thread(a,"B窗口").start();
new Thread(a,"C窗口").start();

}



}

class Ticket implements Runnable{

int ticket = 100;

synchronized private void doWork() throws InterruptedException {

if (ticket>0){
System.out.println(Thread.currentThread().getName()+"售票,票號為:"+ticket);
ticket--;
Thread.sleep(100);
}

}

@Override
public void run() {

for (int i=1;i<=100;i++){
try {
doWork();
} catch (InterruptedException e) {
e.printStackTrace();
}
}



}

}

注意:

不要使用synchronized修飾run方法,修飾之后,某一個線程就執行完了所有的功能好比是多個線程出現串行.

 

解決方案:把需要同步操作的代碼定義在一個新的方法中,並且該方法使用synchronized修飾,再在run方法中調用該新的方法即可.

 

實際上,同步代碼塊和同步方法差不了多少,在本質上是一樣的,兩者都用了一個關鍵字synchronizedsynchronized保證了多線程並發訪問時的同步操作,避免線程的安全性問題,但是有一個弊端,就是使用synchronized的方法/代碼塊的性能比不用要低一些,因此如果要用synchronized,建議盡量減小synchronized的作用域。

 

方式3:同步鎖(鎖機制)

 

Lock機制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作,同步代碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現面向對象.

 
         
package com.yunqing.ssm.test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* 存在線程安全問題
*/
public class TestRunnable {

public static void main(String[] args) {
Runnable a = new Ticket();
new Thread(a,"A窗口").start();
new Thread(a,"B窗口").start();
new Thread(a,"C窗口").start();

}



}

class Ticket implements Runnable{

int ticket = 100;

//創建鎖對象
private final Lock lock = new ReentrantLock();

private void doWork(){
//進入方法,立馬加鎖
lock.lock();


try {

if (ticket>0){
System.out.println(Thread.currentThread().getName()+"售票,票號為:"+ticket);
ticket--;
Thread.sleep(100);
}


} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}


}

@Override
public void run() {

for (int i=1;i<=100;i++){
doWork();
}



}

}
 
        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM