多線程中如何解決資源爭搶的問題(synchronized關鍵字)?

說到解決多線程中資源爭搶的問題,大多數第一個想到的關鍵字就是synchronized,它能夠保證多個線程之間資源訪問的同步性(即它修飾的方法或者代碼塊在任意時刻只能有一個線程執行)。

/**
 * 一個座位一個人 兩個電影窗口賣票 不加鎖會造成什麼結果?
 */
public class Seat implements Runnable {
	private int seatNumber = 100;

	@Override
	public void run() {
		while (true) {
			if (seatNumber > 0) {
				try {
					Thread.sleep(30);
					--seatNumber;
					System.out.println(Thread.currentThread().getName() + "佔用1個座位,還剩餘 " + seatNumber + "個座位");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} else {
				System.out.println(Thread.currentThread().getName() + ":不好意思,票賣完了!");
				break;
			}
		}
	}

	public static void main(String[] args) {
		Seat mr = new Seat();
		Thread t1 = new Thread(mr, "A窗口");
		Thread t2 = new Thread(mr, "B窗口");
		t1.start();
		t2.start();
	}
}

可以明顯看出,不加synchronized,無法解決資源爭搶的問題,我們可以這樣改下代碼:

@Override
	public void run() {
		while (true) {
			synchronized (this) {
				if (seatNumber > 0) {
					try {
						Thread.sleep(30);
						--seatNumber;
						System.out.println(Thread.currentThread().getName() + "佔用1個座位,還剩餘 " + seatNumber + "個座位");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				} else {
					System.out.println(Thread.currentThread().getName() + ":不好意思,票賣完了!");
					break;
				}
			}
		}
	}

加了synchronized,這個問題就解決了。需要知道的是,早期(Jdk1.6以前)synchronized屬於重量級鎖,效率低下,而在Java6之後,Java官方從JVM層面對synchronized進行了大的優化,如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。

那麼,如何使用synchronized關鍵字呢?
1)修飾實例方法:作用於當前對象實例,即鎖對象即爲當前對象。

/**
 * synchronized修飾實例方法
 */
public class ExampleTest implements Runnable {
	private int seatNumber = 100;

	@Override
	public void run() {
		while (true) {

			if (save()) {
				try {
					Thread.sleep(30);
					--seatNumber;
					System.out.println(Thread.currentThread().getName() + "佔用1個座位,還剩餘 " + seatNumber + "個座位");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} else {
				System.out.println(Thread.currentThread().getName() + ":不好意思,票賣完了!");
				break;
			}

		}
	}

	private synchronized boolean save() {
		if (seatNumber > 0) {
			return true;
		} else {
			return false;
		}
	}

	public static void main(String[] args) {
		ExampleTest mr = new ExampleTest();
		Thread t1 = new Thread(mr, "A窗口");
		Thread t2 = new Thread(mr, "B窗口");
		t1.start();
		t2.start();
	}
}
執行結果:
A窗口占用1個座位,還剩餘 99個座位
B窗口占用1個座位,還剩餘 99個座位
B窗口占用1個座位,還剩餘 98個座位
A窗口占用1個座位,還剩餘 97個座位
A窗口占用1個座位,還剩餘 95個座位
B窗口占用1個座位,還剩餘 95個座位
A窗口占用1個座位,還剩餘 93個座位
B窗口占用1個座位,還剩餘 93個座位
A窗口占用1個座位,還剩餘 91個座位
B窗口占用1個座位,還剩餘 91個座位
B窗口占用1個座位,還剩餘 90個座位
A窗口占用1個座位,還剩餘 90個座位
A窗口占用1個座位,還剩餘 88個座位
B窗口占用1個座位,還剩餘 88個座位
B窗口占用1個座位,還剩餘 87個座位
A窗口占用1個座位,還剩餘 86個座位
B窗口占用1個座位,還剩餘 84個座位
A窗口占用1個座位,還剩餘 84個座位
B窗口占用1個座位,還剩餘 83個座位
A窗口占用1個座位,還剩餘 82個座位
B窗口占用1個座位,還剩餘 81個座位
A窗口占用1個座位,還剩餘 80個座位
B窗口占用1個座位,還剩餘 79個座位
A窗口占用1個座位,還剩餘 78個座位
B窗口占用1個座位,還剩餘 76個座位
A窗口占用1個座位,還剩餘 76個座位
B窗口占用1個座位,還剩餘 75個座位
A窗口占用1個座位,還剩餘 74個座位
A窗口占用1個座位,還剩餘 73個座位
B窗口占用1個座位,還剩餘 72個座位
B窗口占用1個座位,還剩餘 70個座位
A窗口占用1個座位,還剩餘 70個座位
B窗口占用1個座位,還剩餘 69個座位
A窗口占用1個座位,還剩餘 68個座位
B窗口占用1個座位,還剩餘 66個座位
A窗口占用1個座位,還剩餘 66個座位
B窗口占用1個座位,還剩餘 65個座位
A窗口占用1個座位,還剩餘 64個座位
A窗口占用1個座位,還剩餘 63個座位
B窗口占用1個座位,還剩餘 62個座位
A窗口占用1個座位,還剩餘 60個座位
B窗口占用1個座位,還剩餘 60個座位
B窗口占用1個座位,還剩餘 59個座位
A窗口占用1個座位,還剩餘 58個座位
B窗口占用1個座位,還剩餘 57個座位
A窗口占用1個座位,還剩餘 56個座位
B窗口占用1個座位,還剩餘 55個座位
A窗口占用1個座位,還剩餘 54個座位
B窗口占用1個座位,還剩餘 53個座位
A窗口占用1個座位,還剩餘 52個座位
B窗口占用1個座位,還剩餘 51個座位
A窗口占用1個座位,還剩餘 50個座位
A窗口占用1個座位,還剩餘 49個座位
B窗口占用1個座位,還剩餘 48個座位
B窗口占用1個座位,還剩餘 47個座位
A窗口占用1個座位,還剩餘 47個座位
B窗口占用1個座位,還剩餘 46個座位
A窗口占用1個座位,還剩餘 45個座位
A窗口占用1個座位,還剩餘 44個座位
B窗口占用1個座位,還剩餘 43個座位
A窗口占用1個座位,還剩餘 42個座位
B窗口占用1個座位,還剩餘 42個座位
A窗口占用1個座位,還剩餘 41個座位
B窗口占用1個座位,還剩餘 40個座位
A窗口占用1個座位,還剩餘 38個座位
B窗口占用1個座位,還剩餘 38個座位
A窗口占用1個座位,還剩餘 37個座位
B窗口占用1個座位,還剩餘 36個座位
A窗口占用1個座位,還剩餘 35個座位
B窗口占用1個座位,還剩餘 34個座位
A窗口占用1個座位,還剩餘 33個座位
B窗口占用1個座位,還剩餘 32個座位
A窗口占用1個座位,還剩餘 31個座位
B窗口占用1個座位,還剩餘 30個座位
A窗口占用1個座位,還剩餘 29個座位
B窗口占用1個座位,還剩餘 28個座位
A窗口占用1個座位,還剩餘 27個座位
B窗口占用1個座位,還剩餘 26個座位
A窗口占用1個座位,還剩餘 25個座位
B窗口占用1個座位,還剩餘 24個座位
A窗口占用1個座位,還剩餘 23個座位
B窗口占用1個座位,還剩餘 22個座位
A窗口占用1個座位,還剩餘 21個座位
B窗口占用1個座位,還剩餘 20個座位
A窗口占用1個座位,還剩餘 19個座位
B窗口占用1個座位,還剩餘 18個座位
A窗口占用1個座位,還剩餘 17個座位
B窗口占用1個座位,還剩餘 16個座位
A窗口占用1個座位,還剩餘 15個座位
B窗口占用1個座位,還剩餘 14個座位
A窗口占用1個座位,還剩餘 13個座位
B窗口占用1個座位,還剩餘 12個座位
A窗口占用1個座位,還剩餘 11個座位
B窗口占用1個座位,還剩餘 10個座位
A窗口占用1個座位,還剩餘 9個座位
B窗口占用1個座位,還剩餘 8個座位
A窗口占用1個座位,還剩餘 7個座位
B窗口占用1個座位,還剩餘 6個座位
A窗口占用1個座位,還剩餘 5個座位
B窗口占用1個座位,還剩餘 4個座位
A窗口占用1個座位,還剩餘 3個座位
B窗口占用1個座位,還剩餘 2個座位
A窗口占用1個座位,還剩餘 1個座位
B窗口占用1個座位,還剩餘 0個座位
B窗口:不好意思,票賣完了!
A窗口占用1個座位,還剩餘 -1個座位
A窗口:不好意思,票賣完了!

2)修飾靜態方法:作用的範圍是整個靜態方法,作用的對象是這個類的所有對象,因爲靜態成員不屬於任何一個實例對象,是類成員(static 表明這是該類的一個靜態資源,不管new了多少個對象,只有一份,所以對該類的所有對象都加了鎖)。

所以如果一個線程A調用一個實例對象的非靜態synchronized方法,而線程B需要調用這個實例對象所屬類的靜態 synchronized方法,是允許的,不會發生互斥現象,因爲訪問靜態 synchronized方法佔用的鎖是當前類的鎖,而訪問非靜態synchronized 方法佔用的鎖是當前實例對象鎖。

/**
 * synchronized修飾靜態方法
 */
public class StaticTest implements Runnable {

	private static int seatNumber = 100;

	@Override
	public void run() {
		while (true) {
			if (save()) {
				try {
					Thread.sleep(30);
					--seatNumber;
					System.out.println(Thread.currentThread().getName() + "佔用1個座位,還剩餘 " + seatNumber + "個座位");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			} else {
				System.out.println(Thread.currentThread().getName() + ":不好意思,票賣完了!");
				break;
			}
		}
	}

	private synchronized static boolean save() {
		if (seatNumber > 0) {
			return true;
		} else {
			return false;
		}
	}

	public static void main(String[] args) {
		StaticTest mr = new StaticTest();
		Thread t1 = new Thread(mr, "A窗口");
		Thread t2 = new Thread(mr, "B窗口");
		t1.start();
		t2.start();
	}
}
A窗口占用1個座位,還剩餘 98個座位
B窗口占用1個座位,還剩餘 98個座位
A窗口占用1個座位,還剩餘 97個座位
B窗口占用1個座位,還剩餘 96個座位
A窗口占用1個座位,還剩餘 95個座位
B窗口占用1個座位,還剩餘 94個座位
B窗口占用1個座位,還剩餘 93個座位
A窗口占用1個座位,還剩餘 92個座位
B窗口占用1個座位,還剩餘 91個座位
A窗口占用1個座位,還剩餘 90個座位
B窗口占用1個座位,還剩餘 89個座位
A窗口占用1個座位,還剩餘 88個座位
B窗口占用1個座位,還剩餘 87個座位
A窗口占用1個座位,還剩餘 86個座位
B窗口占用1個座位,還剩餘 85個座位
A窗口占用1個座位,還剩餘 84個座位
B窗口占用1個座位,還剩餘 83個座位
A窗口占用1個座位,還剩餘 82個座位
B窗口占用1個座位,還剩餘 81個座位
A窗口占用1個座位,還剩餘 80個座位
B窗口占用1個座位,還剩餘 79個座位
A窗口占用1個座位,還剩餘 78個座位
B窗口占用1個座位,還剩餘 77個座位
A窗口占用1個座位,還剩餘 76個座位
B窗口占用1個座位,還剩餘 75個座位
A窗口占用1個座位,還剩餘 74個座位
B窗口占用1個座位,還剩餘 73個座位
A窗口占用1個座位,還剩餘 72個座位
B窗口占用1個座位,還剩餘 71個座位
A窗口占用1個座位,還剩餘 70個座位
B窗口占用1個座位,還剩餘 69個座位
A窗口占用1個座位,還剩餘 68個座位
B窗口占用1個座位,還剩餘 67個座位
A窗口占用1個座位,還剩餘 66個座位
B窗口占用1個座位,還剩餘 65個座位
A窗口占用1個座位,還剩餘 64個座位
B窗口占用1個座位,還剩餘 63個座位
A窗口占用1個座位,還剩餘 62個座位
B窗口占用1個座位,還剩餘 61個座位
A窗口占用1個座位,還剩餘 60個座位
B窗口占用1個座位,還剩餘 59個座位
A窗口占用1個座位,還剩餘 58個座位
B窗口占用1個座位,還剩餘 57個座位
A窗口占用1個座位,還剩餘 56個座位
B窗口占用1個座位,還剩餘 55個座位
A窗口占用1個座位,還剩餘 54個座位
B窗口占用1個座位,還剩餘 53個座位
A窗口占用1個座位,還剩餘 52個座位
B窗口占用1個座位,還剩餘 51個座位
A窗口占用1個座位,還剩餘 50個座位
B窗口占用1個座位,還剩餘 49個座位
A窗口占用1個座位,還剩餘 48個座位
B窗口占用1個座位,還剩餘 47個座位
A窗口占用1個座位,還剩餘 46個座位
B窗口占用1個座位,還剩餘 45個座位
A窗口占用1個座位,還剩餘 44個座位
B窗口占用1個座位,還剩餘 43個座位
A窗口占用1個座位,還剩餘 42個座位
B窗口占用1個座位,還剩餘 41個座位
A窗口占用1個座位,還剩餘 40個座位
B窗口占用1個座位,還剩餘 39個座位
A窗口占用1個座位,還剩餘 38個座位
B窗口占用1個座位,還剩餘 37個座位
A窗口占用1個座位,還剩餘 36個座位
B窗口占用1個座位,還剩餘 35個座位
A窗口占用1個座位,還剩餘 34個座位
B窗口占用1個座位,還剩餘 33個座位
A窗口占用1個座位,還剩餘 32個座位
B窗口占用1個座位,還剩餘 31個座位
A窗口占用1個座位,還剩餘 30個座位
B窗口占用1個座位,還剩餘 29個座位
A窗口占用1個座位,還剩餘 28個座位
B窗口占用1個座位,還剩餘 27個座位
A窗口占用1個座位,還剩餘 26個座位
B窗口占用1個座位,還剩餘 25個座位
A窗口占用1個座位,還剩餘 24個座位
B窗口占用1個座位,還剩餘 23個座位
A窗口占用1個座位,還剩餘 22個座位
B窗口占用1個座位,還剩餘 21個座位
A窗口占用1個座位,還剩餘 20個座位
B窗口占用1個座位,還剩餘 19個座位
A窗口占用1個座位,還剩餘 18個座位
B窗口占用1個座位,還剩餘 17個座位
A窗口占用1個座位,還剩餘 16個座位
B窗口占用1個座位,還剩餘 15個座位
A窗口占用1個座位,還剩餘 14個座位
B窗口占用1個座位,還剩餘 13個座位
A窗口占用1個座位,還剩餘 12個座位
B窗口占用1個座位,還剩餘 11個座位
A窗口占用1個座位,還剩餘 10個座位
B窗口占用1個座位,還剩餘 9個座位
A窗口占用1個座位,還剩餘 8個座位
B窗口占用1個座位,還剩餘 7個座位
A窗口占用1個座位,還剩餘 6個座位
B窗口占用1個座位,還剩餘 5個座位
A窗口占用1個座位,還剩餘 4個座位
B窗口占用1個座位,還剩餘 3個座位
A窗口占用1個座位,還剩餘 2個座位
B窗口占用1個座位,還剩餘 1個座位
A窗口占用1個座位,還剩餘 0個座位
A窗口:不好意思,票賣完了!
B窗口占用1個座位,還剩餘 -1個座位
B窗口:不好意思,票賣完了!

3)修飾代碼塊:指定加鎖對象,對給定對象加鎖,即鎖對象即爲小括號中的對象。

>>>>>總結<<<<<

synchronized關鍵字的作用就是爲了保證同一時刻只能有1個線程執行Synchronized修飾的方法和代碼塊,需要注意的是,實際同步的是對象或者類,而非代碼。所以,此時此刻,其他線程必須等待當前線程執行完畢纔可以繼續執行。所以,synchronized(解決的多線程併發其實是阻塞型併發)一般用在以下場景:

1)修飾實例方法 / 代碼塊時,(同步)保護的是同一個對象方法的調用 & 當前實例對象


2)修飾靜態方法 / 代碼塊時,(同步)保護的是 靜態方法的調用 & class 類對象

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