多线程中如何解决资源争抢的问题(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 类对象

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