寫在前面
本文全文以售票系統爲例,簡訴了java多線程間共享數據的兩種方式、線程同步。文章可能還有很多不足,請大家諒解,歡迎大佬提意見。
本文使用到的東西
- java
- 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實現的是修飾的內容同步。有不清楚的地方歡迎評論留言,看到的我都會回覆的。本文到此結束,有什麼不足的地方請大家不吝指正。