java多線程共享數據、線程同步與互斥

寫在前面

本文全文以售票系統爲例,簡訴了java多線程間共享數據的兩種方式、線程同步。文章可能還有很多不足,請大家諒解,歡迎大佬提意見。

本文使用到的東西

  1. java
  2. eclipse 2019-11

1.多線程共享數據

1.1 共享Runnable

  當多個線程執行的內容相同,可以採用共享Runnable接口實現類對象的方式共享數據,將共享的數據作爲Runnable對象的成員變量。
  這裏我們以包含多個售票處的售票系統爲例,每一個售票處一個線程,多個線程共享餘票數變量。

class TicketRunnable implements Runnable{
	private int num;
	
	public TicketRunnable(int num) {
		this.num=num;
	}
	@Override
	public void run() {
		for(int i=0;i<5;i++) {	//出售5張票
			if(num>0) {
				num--;
				System.out.println(Thread.currentThread().getName()+":售票1張,餘票"+num);
			}else {
				System.out.println(Thread.currentThread().getName()+":暫時無餘票");
			}
		}
	}
}
public class 多線程共享數據 {
	public static void main(String[] args) {
		Runnable runnable = new TicketRunnable(8);	//初始化8張餘票
		new Thread(runnable,"武漢售票點").start();
		new Thread(runnable,"北京售票點").start();
	}
}

1.2 封裝數據爲對象

  當多個線程執行的是相同的操作時可以採用共享Runnable對象的方法來共享變量,執行不同操作時這個方法就不適用,可以將共享的數據封裝成對象,多個線程共享該對象。
  以售票系統爲例,一個線程執行退票,一個線程執行售票。

class Ticket{
	private int num;
	public Ticket(int num) {
		this.num=num;
	}
	public void sell() {
		if(num>0) {
			num--;
			System.out.println(Thread.currentThread().getName()+":售票1張,餘票"+num);
		}else {
			System.out.println(Thread.currentThread().getName()+":暫時無餘票");
		}
	}
	public void returned() {
		num++;
		System.out.println(Thread.currentThread().getName()+":退票1張,餘票"+num);
	}
}
public class 多線程共享數據 {
	public static void main(String[] args) {
		Ticket ticket = new Ticket(8);	//初始化爲8張票
		new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=0;i<8;i++) {
					ticket.sell();//售票
				}
			}
		},"售票處").start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				for(int i=0;i<8;i++) {
					ticket.returned();//退票
				}
			}
		},"退票處").start();
	}
}

2.線程同步與互斥

2.1 上述代碼存在的問題

以共享Runnable對象實現同步的方式爲例,運行該程序,運行結果如下:

武漢售票點:售票1張,餘票6
武漢售票點:售票1張,餘票5
北京售票點:售票1張,餘票6
武漢售票點:售票1張,餘票4
武漢售票點:售票1張,餘票2
北京售票點:售票1張,餘票3
北京售票點:售票1張,餘票0
北京售票點:暫時無餘票
北京售票點:暫時無餘票
武漢售票點:售票1張,餘票1

  我們設置的初始票數爲8,查看運行結果,餘票數量並不是從7開始遞減,而是從6開始,而且並不是遞減。出現該問題是因爲武漢售票點將票數減1還未輸出的時候,北京售票點也將票數減1,這時候輸出結果就是6了。不是按遞減輸出也同樣是因爲讀取了數據還未輸出,另一個線程執行了賣票輸出。對TicketRunnable的run()方法稍加修改,修改爲

	@Override
	public void run() {
		for(int i=0;i<5;i++) {	//出售5張票
			synchronized (this) {
				if(num>0) {
					num--;
					System.out.println(Thread.currentThread().getName()+":售票1張,餘票"+num);
				}else {
					System.out.println(Thread.currentThread().getName()+":暫時無餘票");
				}
			}
		}
	}

此時,又是按遞減順序輸出程序內容,因爲synchronized給代碼塊添加了同步鎖,將修改值和取值的操作進行了同步,所以不會在出現亂序、輸出餘票不正確的情況。

2.2 同步與互斥

1. 什麼是互斥?
在計算機中很多資源都是有限的,這種有限資源叫做臨界資源。多個進程爭搶同一個臨界資源,搶到了可以運行,沒搶到就無法運行。互斥就是爭奪臨界資源進程的間接制約關係。 例如多個打印線程爭奪一臺打印機資源,進程間就形成了互斥。

2. 什麼是同步?
同步是協調多個相互關聯線程合作完成任務,彼此之間存在一定約束,執行順序往往是有序的。 同步是進程間的直接制約關係,例如供銷系統,當倉庫滿了需要停止供貨,倉庫空了無法出貨,此時供貨進程和銷貨進程就形成了同步關係。

2.3 synchronized實現同步

synchronized可用於修飾方法、靜態方法和代碼塊

//對象鎖,修飾方法
synchronized void a() {

}
//類鎖,修飾靜態方法
synchronized static void b() {

}
void c() {
	//對象鎖,修飾代碼塊
	synchronized (this) {
			
	}
	//類鎖,修飾代碼塊
	synchronized (Ticket.class) {

	}	
}

2.4 ReentrantLock實現同步

1.使用

ReentrantLock lock = new ReentrantLock();
lock.lock();	//加鎖
lock.unlock();	//解鎖

2.實現上述的售票同步

class TicketRunnable implements Runnable{
	private int num;
	ReentrantLock lock = new ReentrantLock();
	
	public TicketRunnable(int num) {
		this.num=num;
	}
	@Override
	public void run() {
		for(int i=0;i<5;i++) {	//出售5張票
			lock.lock();
			if(num>0) {
				num--;
				System.out.println(Thread.currentThread().getName()+":售票1張,餘票"+num);
			}else {
				System.out.println(Thread.currentThread().getName()+":暫時無餘票");
			}
			lock.unlock();
		}
	}
}
public class 多線程共享數據 {
	public static void main(String[] args) {
		Runnable runnable = new TicketRunnable(8);	//初始化8張餘票
		new Thread(runnable,"武漢售票點").start();
		new Thread(runnable,"北京售票點").start();
	}
}

3.總結

synchronized實現的是同步還是互斥這一點有些難理解,網上也有說synchronized是互斥鎖的,synchronized實現的是修飾的內容同步。有不清楚的地方歡迎評論留言,看到的我都會回覆的。本文到此結束,有什麼不足的地方請大家不吝指正。

發佈了44 篇原創文章 · 獲贊 29 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章