天天看點

線程同步,可重入鎖,synchronized

多個線程操作同一個資源

并發:

同一個對象被多個線程同時操作

        上萬人同時搶100張票

        兩個銀行同時取錢:銀行卡隻有1000元,你和妻子一起取錢,然後你妻子可能取到1000元,此時銀行卡裡已經沒有錢了,那麼如果不同步的話,你可能就取到-1000元,這可能嗎?

處理多線程問題時,多個線程通路同一個對象,并且某些線程還想要修改這個對象,這時候我們就需要線程同步,線程同步其實就是一種等待機制,多個需要同時通路此對象的線程進入這個對象的等待池形成隊列,等待前面的線程使用完畢,下一個線程再使用

由于同一程序的多個線程共享同一塊存儲空間,在帶來友善的同時,也帶來了通路沖突問題,為了保證資料在方法中被通路時的正确性,在通路時加入 鎖機制 synchronized,當一個線程獲得對象的排它鎖,獨占資源,其他線程必須等待,使用後釋放鎖即可,存在以下問題:

1)一個線程持有鎖會導緻其他所有需要此鎖的線程挂起

2)在多線程競争下,加鎖,釋放鎖會導緻比較多的上下文切換和排程延時,引起性能問題

3)如果一個優先級高的線程等待一個優先級低的線程釋放鎖,會導緻優先級倒置,引起性能問題

不安全的買票:為什麼會出現賣出第-1張票的情況呢?

//不安全的 買票
public class Test  {
	public static void main(String[] args) throws InterruptedException {	
		Buyticket buyticket=new Buyticket();
		new Thread(buyticket,"苦逼的我").start();
		new Thread(buyticket,"牛逼的你們").start();
		new Thread(buyticket,"可惡的黃牛黨").start();	
	}
}
	
class Buyticket implements Runnable{
	private boolean flag=true;
	private int tickets=10;
	//買票
		public void run() {
			while(flag) {
				try {
					isticket();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			
		}
	private void isticket() throws InterruptedException {
		//判斷是否有票
		if(tickets<=0) {
			flag=false;
			return;
		}
		Thread.sleep(100);
		
		System.out.println(Thread.currentThread().getName()+tickets--);
	}	
}
	
           
//輸出結果
可惡的黃牛黨10
牛逼的你們9
苦逼的我8
可惡的黃牛黨7
牛逼的你們6
苦逼的我5
可惡的黃牛黨4
牛逼的你們3
苦逼的我2
可惡的黃牛黨1
牛逼的你們0-----------------------第0張票
苦逼的我-1 ------------------看到這個結果我們思考,為什麼賣出了第-1張票
           

分析問題:

大家一進來都看到10張票,然後開始購買,當賣到最後一張票時,第一個人看到還有一張票,然後他就打算買,但是它剛買到這張票,程式還沒來得及執行到tickets--這一步,然後CPU又被另一個人搶占了,他沒發現票沒了,他看到的是票還剩一張,然後他又買票,當他買了之後,可能程式被上一個人執行,繼續在tickets--這一步,然後他将tickets 減減為0,此時另一個人也執行到tickets--,将tickets的0又減1變為-1

是以當線程越多的時候,不僅會出現0和-1可能也出現-2,-3,乃至-4

同步方法:

由于我們可以通過private關鍵字來保證資料對象隻能被方法通路,是以我們隻需要針對方法提出一套機制,這套機制就是synchronized關鍵字,它包括兩種用法:

synchronized方法和synchronized塊

        同步方法:public synchronized void method(int a){}

synchronized方法控制對“對象”的通路,每個對象對應一把鎖,每個synchronized方法都必須或得調用該方法的對象的鎖才能執行,否則會線程阻塞,方法一旦執行,就獨占該鎖,直到該方法傳回才釋放鎖,後面被阻塞的線程才能獲得這個鎖,繼續執行

        缺陷:若将一個大的方法申明為synchronized将會影響效率

同步方法的弊端:

線程同步,可重入鎖,synchronized
線程同步,可重入鎖,synchronized

 死鎖:

線程同步,可重入鎖,synchronized
線程同步,可重入鎖,synchronized

死鎖的案例:

//多個線程互相抱着對方需要的資源,然後形成僵持
public class StaticProxy  {
	public static void main(String[] args) throws InterruptedException {	
		Makeup makeup1=new Makeup(0,"灰姑娘");
		Makeup makeup2=new Makeup(1,"白雪公主");
		makeup1.start();
		makeup2.start();
		
	}
}
	
class Makeup extends Thread{
	//需要的資源隻有一份,用static保證隻有一份
	static Lipstick lipstick=new Lipstick();
	static Mirrors mirrors=new Mirrors();
	int choice;//選擇
	String girlName;//使用化妝品的人
	Makeup(int choice,String girlName) {
		this.girlName=girlName;
		this.choice=choice;
	}
	public void run() {
		makeup();
	}
	
	public  void makeup(){
         //這個地方灰姑娘拿着口紅想要鏡子的鎖,白雪公主拿着鏡子的鎖想要口紅,雙方都不釋放鎖,形成了一個僵持的局面
		if(choice==0) {
			synchronized(lipstick) {
				System.out.println(this.girlName+"獲得口紅的鎖");
				synchronized(mirrors) {
					System.out.println(this.girlName+"獲得鏡子的鎖");
				}
			}
		}else {
			synchronized(mirrors) {
				System.out.println(this.girlName+"獲得鏡子的鎖");
				synchronized(lipstick) {
					System.out.println(this.girlName+"獲得口紅的鎖");
				}
			}
		}
	}
	
}
class Lipstick{	
}

class Mirrors{	
}
           

解決方案:

//多個線程互相抱着對方需要的資源,然後形成僵持
public class StaticProxy  {
	public static void main(String[] args) throws InterruptedException {	
		Makeup makeup1=new Makeup(0,"灰姑娘");
		Makeup makeup2=new Makeup(1,"白雪公主");
		makeup1.start();
		makeup2.start();
		
	}
}
	
class Makeup extends Thread{
	//需要的資源隻有一份,用static保證隻有一份
	static Lipstick lipstick=new Lipstick();
	static Mirrors mirrors=new Mirrors();
	int choice;//選擇
	String girlName;//使用化妝品的人
	Makeup(int choice,String girlName) {
		this.girlName=girlName;
		this.choice=choice;
	}
	public void run() {
		makeup();
	}
	
	public  void makeup(){
		if(choice==0) {
			synchronized(lipstick) {
				System.out.println(this.girlName+"獲得口紅的鎖");
				
			}
			synchronized(mirrors) {
				System.out.println(this.girlName+"獲得鏡子的鎖");
			}
		}else {
			synchronized(mirrors) {
				System.out.println(this.girlName+"獲得鏡子的鎖");
				
			}
			synchronized(lipstick) {
				System.out.println(this.girlName+"獲得口紅的鎖");
			}
		}
	}
	
}
class Lipstick{	
}

class Mirrors{	
}
           

lock鎖

線程同步,可重入鎖,synchronized

 可重入鎖代碼示例

public class StaticProxy  {
	public static void main(String[] args) throws InterruptedException {	
		Buytickets buytickets=new Buytickets();
		new Thread(buytickets).start();
		new Thread(buytickets).start();
		new Thread(buytickets).start();
		
	}
}
	
class Buytickets implements Runnable{
	private static int tickets=10;
	private static boolean flag=true;
	ReentrantLock reentrantLock=new ReentrantLock();
	public void run() {
		while(flag) {
			try {
				reentrantLock.lock();
				if(tickets>0) {
					try {
						Thread.sleep(1000);
						System.out.println(Thread.currentThread().getName()+"正在買第"+tickets--);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					
				}else {
					flag=false;
					break;
				}
			}finally {
				reentrantLock.unlock();
			}
			
		}
	}
}
           
class A{
    private final ReentrantLock lock=new ReenTrantLock();
    public void m(){
        lock.lock();
        try{
            //保證線程安全的代碼;
        }finally{
            lock.unlock();
            //如果同步代碼有異常,要将unlock()寫入finally語句塊
        }
    }
}
           

synchronized和Lock的對比

線程同步,可重入鎖,synchronized

 線程協作

線程同步,可重入鎖,synchronized

 sleep抱着鎖睡覺,wait放開鎖睡覺

生産者消費者問題:

線程同步,可重入鎖,synchronized

 線程池:

線程同步,可重入鎖,synchronized