【多線程 三】線程同步的幾種方式詳解

1、問題引入

當多個線程共同訪問共享資源的時候,就會發生線程安全的問題,比如如下的程序:

三個窗口同時賣一百張票,但最後出現沒有存在的票(0 -1票)
代碼:

public class TicketRunnableImpl implements Runnable{
    private int ticket=100;
    @Override
    public void run() {
        while(true){
            if(ticket>0)
                //使用sleep 模擬一下出票時間
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 當前線程對應的名字
                String name=Thread.currentThread().getName();
                System.out.println(name+"正在賣:"+ticket--);
            }
        }
    }
}
public class Demo01Ticket {
    public static void main(String[] args) {
        TicketRunnableImpl tck=new TicketRunnableImpl();
        Thread thread1= new Thread(tck,"窗口1");
        Thread thread2 = new Thread(tck,"窗口2");
        Thread thread3 = new Thread(tck,"窗口3");
        // 同時賣票
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

在這裏插入圖片描述

以上,最後窗口賣出了不存在的票,就是說最後窗口3已經把最後一個票賣出去了,但是窗口 1, 2還都不知道。還有可能同一個票被賣了兩回,這種問題就是線程不安全,當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來執行,導致共享數據的錯誤。

解決線程不安全的辦法就是線程同步。

2、線程同步

由於多個線程執行的不確定性會引起執行結果的不穩定,所以對多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行。
在這裏插入圖片描述

2.1 同步代碼塊實現線程同步
格式:
   synchronized(同步鎖){ 
       需要同步操作的代碼 
   } 

同步鎖:

對象的同步鎖只是一個概念,可以想象爲在對象上標記了一個鎖.

  1. 鎖對象 可以是任意類型。

  2. 多個線程對象 要使用同一把鎖。

注意:在任何時候,最多允許一個線程擁有同步鎖,誰拿到鎖就進入代碼塊,其他的線程只能在外等着

使用同步代碼塊解決代碼:
public class TicketRunnableImpl implements Runnable {
    private int ticket = 100;
    Object lock = new Object();

    @Override
    public void run() {
        while (true) {
            synchronized (lock) {
                if (ticket > 0) {
                    //使用sleep 模擬一下出票時間
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 當前線程對應的名字
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "正在賣:" + ticket--);
                }
            }
        }
    }
}

結果,沒有在出現0 -1票

在這裏插入圖片描述

2.2 同步代碼塊實現線程同步
public synchronized void method(){ 
       可能會產生線程安全問題的代碼 
   } 
public class TicketRunnableImpl implements Runnable{
    private int ticket = 100;
    Object lock = new Object();
    @Override
    public void run() {
        while (true) {
            sellTicket();
        }
    }

        *   鎖對象 是 誰調用這個方法 就是誰
        *   隱含 鎖對象 就是  this 
    public  synchronized void sellTicket(){
            if (ticket > 0) {
                //使用sleep 模擬一下出票時間
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 當前線程對應的名字
                String name = Thread.currentThread().getName();
                System.out.println(name + "正在賣:" + ticket--);
            }
    }
}

那麼此時的同步鎖是誰:

對於非static方法,同步鎖就是this。
對於static方法,我們使用當前方法所在類的字節碼對象(類名.class)。

2.3 Lock 鎖實現線程同步

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

ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的併發性和內存語義,在實現線程安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖luck()、釋放鎖unlock()。我們在編碼的過程中一定要記得最後要釋放鎖,所以要避免忘記釋放鎖(發生死鎖),要提前寫好下面的模板

public void m(){
	lock.lock();
	try{
		//保證線程安全的代碼;
	}
	finally{
		lock.unlock();  
	}
}

對應代碼:

   private  int ticket =100;
    Lock lock=new ReentrantLock();
    @Override
    public void run() {
        while (true){
            lock.lock();
            try {
                if (ticket > 0) {
                    //使用sleep 模擬一下出票時間
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    // 當前線程對應的名字
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "正在賣:" + ticket--);
                }
            }finally {
                lock.unlock();
            }
        }
    }
3、synchronized與Lock 的對比

1、二者都可以解決線程安全問題
2、Lock是顯示鎖(手動開啓和關閉鎖),synchronized是隱式鎖,在執行完相應的代碼以後,自動釋放鎖。
3、Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖

4、釋放鎖的操作

當前線程的同步方法、同步代碼塊執行結束。

當前線程在同步代碼塊、同步方法中遇到breakreturn終止了該代碼塊、
該方法的繼續執行。

當前線程在同步代碼塊、同步方法中出現了未處理的ErrorException,導
致異常結束。

當前線程在同步代碼塊、同步方法中執行了線程對象的**wait()**方法,當前線
程暫停,並釋放鎖。

注意:yield() 和 sleep並不會釋放鎖

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章