1. 程式人生 > >鎖機制——解決多執行緒的資料共享帶來的同步問題

鎖機制——解決多執行緒的資料共享帶來的同步問題

“非執行緒安全”——多個執行緒同一個物件中的例項變數進行併發訪問時發生,產生的後果就是“髒讀”,也就是取到的資料其實是被更改過的。

1、方法內的變數為執行緒安全的
方法內部的私有變數,則不存在“非執行緒安全”的問題,所得結果也就是“執行緒安全”的。

2、例項變數非執行緒安全
如果多個執行緒共同訪問一個物件中的例項變數,則有可能出現“非執行緒安全”問題。

 

對於上一篇《多執行緒的資料共享》作進一步改進,解決同步問題

 

利用同步塊可以保證一個執行緒在一個程式碼塊中訪問同步資源未結束時,阻塞其他執行緒訪問這個同步資源,保證了操作的一致性。

/**
 *
 *         內部類:只想在當前類使用,其他地方用不到的情況下 使用內部類,可以訪問外部類的成員變數
 */
public class TicketSys {
    private int ticketNum = 10;// 例項變數

    private class Window extends Thread {
        public Window(String name) {
            super(name);
        }

        @Override
        public void run() {

            // TODO Auto-generated method stub
            while (ticketNum > 0) {
                synchronized (TicketSys.class) {
                    // 1.收錢
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    // 2.售票
                    System.out.println(Thread.currentThread().getName() + "sell ticket" + ticketNum);
                    // 3.票數減少
                    ticketNum--;
                }
            }
        }

    }

    /**
     * 建立並啟動執行緒
     *
     * @param name
     */
    public void openWindow(String name) {
        new Window(name).start();
    }
}

這是針對繼承Thread實現資料共享的改進,程式碼只多了一個 synchronized (TicketSys.class) {}同步塊,其他地方並無改動,而執行結果則正常了,不會出現同一數字出現多次,也不會出現負數的情況。

呼叫關鍵字synchronized宣告的方法一定是排隊執行的(同步的)。只有共享資源的讀寫訪問才需要同步化,如果不是共享資源,根本沒有同步的必要。


通常還是推薦使用實現Runnable實現資料共享,下面就針對通過實現runnable實現資料共享的程式碼做改進,解決同步問題:

/**
 * @author cuijiao
 *
 */
public class TicketSys implements Runnable {
    private int ticketNum = 10;// 例項變數

    @Override
    public void run() {

        // TODO Auto-generated method stub
        while (ticketNum > 0) {
            sellTicket();
        }
    }

    public synchronized void sellTicket() {// 同步方法:鎖-this,所以當前類在系統中只能new一次
        if (ticketNum > 0) {
            // 1.收錢
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            // 2.售票
            System.out.println(Thread.currentThread().getName() + "sell ticket" + ticketNum);
            // 3.票數減少
            ticketNum--;
        }
    }

}

只是對售票操作抽成了一個方法並加了synchronized 同步鎖,其它程式碼並未改動。

同步方法所在物件在系統中不可new多次,因為同步方法的鎖即this-當前類

 

 

jdk5之後java.util.concurrent.locks提供鎖,比synchronized更加靈活,功能更多

下面使用排他鎖ReentrantLock解決多執行緒的同步問題


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

public class TicketSys implements Runnable {
    private int ticketNum = 10;// 例項變數

    private Lock lock = new ReentrantLock();// 排他鎖

    @Override
    public void run() {

        // TODO Auto-generated method stub
        while (ticketNum > 0) {
            try {
                lock.lock();
                if (ticketNum > 0) {
                    // 1.收錢
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    // 2.售票
                    System.out.println(Thread.currentThread().getName() + "sell ticket" + ticketNum);
                    // 3.票數減少
                    ticketNum--;
                }
            } finally {
                lock.unlock();
            }
        }
    }


}

 如果採用Lock,必須主動去釋放鎖,並且在發生異常時,不會自動釋放鎖。因此一般來說,使用Lock必須在try{}catch{}塊中進行,並且將釋放鎖的操作放在finally塊中進行,以保證鎖一定被被釋放,防止死鎖的發生。通常使用Lock來進行同步的話,需以下面這種形式去使用的:

Lock lock = ...;

lock.lock();

try{

    //處理任務

}catch(Exception ex){

     

}finally{

    lock.unlock();   //釋放鎖

}