【Java】利用synchronized(this)完成线程的临界区

在《【Java】线程并发、互斥与同步》(点击打开链接)中利用了操作系统通过操作信号量控制的原始方法,完成了线程的互斥与同步,说句题外话,其实这个信号量的算法,是著名的迪杰斯特拉创造的,也就是数据结构、计算机网络上面最短路径算法、迪杰斯特拉算法、Dijkstra算法的贡献人。其实Java里面根本就不需要自己定义一个信号量来实现临界区,Java对于临界区的实现早已封装好了,而且synchronized还是Java的关键字。

那么,到底怎么来使用这个关键字呢?下面就对上次《【Java】线程并发、互斥与同步》(点击打开链接)中的程序进行改进,不用信号量,同样来实行里面相应的功能。


一、基本目标

首先来回顾一下《【Java】线程并发、互斥与同步》(点击打开链接)中的程序,说的是有四个黄牛,分别是线程1、线程2、线程3、线程4,目的是要刷光票站里面的20张票,而且各自抢各自的票,不会出现多个黄牛抢一张票的情况,也就是说一个黄牛对应一张票

【Java】利用synchronized(this)完成线程的临界区_第1张图片

而且,每次运行结果不同:

【Java】利用synchronized(this)完成线程的临界区_第2张图片

这个程序上次是以信号量来实现的,下面就用利用synchronized(this)完成线程的临界区,在这里也就是买票的部分,只有一个售票窗口,每次只能容纳一个黄牛去抢票,而且这次每个线程都是以cpu的频率去刷票,刷2000只不虚,每次完成的结果不同,且一张票只落到一个黄牛的手里,

【Java】利用synchronized(this)完成线程的临界区_第3张图片

不会产生以下的无序、乱序的冲突:

【Java】利用synchronized(this)完成线程的临界区_第4张图片

甚至刷20000票都不虚:

【Java】利用synchronized(this)完成线程的临界区_第5张图片

由于程序是健壮的,你改到二十万张也是照样能跑:

【Java】利用synchronized(this)完成线程的临界区_第6张图片

每次运行结果不同是由于CPU分配的资源不同。


二、基本思想:

所谓的利用synchronized(this)完成线程的临界区,就是在一个implements Runnable的进程类里面有如下的一段代码,形成所谓的线程临界区:

【Java】利用synchronized(this)完成线程的临界区_第7张图片

至于许多不知道在写什么的操作系统,对于临界区是这样说的:每个进程中访问临界资源的那段代码称为临界区(Critical Section)(临界资源是一次仅允许一个进程使用的共享资源)。每次只准许一个进程进入临界区,进入后不允许其他进程进入。不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问。

其实,在这里,票就是临界资源,临界区就是黄牛如何买票。


三、制作过程

1、首先和《【Java】线程并发、互斥与同步》(点击打开链接)一样在主函数中开一个进程,其中这个进程里面有四个线程:

public class Threadtest {
	public static void main(String[] args) throws Exception {
		GetTicket getTicket = new GetTicket();
		new Thread(getTicket, "线程1").start();
		new Thread(getTicket, "线程2").start();
		new Thread(getTicket, "线程3").start();
		new Thread(getTicket, "线程4").start();
	}
}

2、这个进程类如下:

class GetTicket implements Runnable {
	//ticket是票数、isNotRead的设置是为了下面对于统计的输出只输出一次
	private int ticket = 200000;
	private boolean isNotRead = true;
	private int count1 = 0;
	private int count2 = 0;
	private int count3 = 0;
	private int count4 = 0;

	public void run() {
		while (this.ticket > 0) {
			//临界区开始,所谓的临界区就是仅能有一个线程访问的部分
			synchronized (this) {
				if (this.ticket > 0) {
					//对票的操作
					this.ticket--;
					System.out.println("票" + (200000 - this.ticket) + "被"
							+ Thread.currentThread().getName() + "买走,当前票数剩余:"
							+ this.ticket);
					//Thread.currentThread().getName()取当前线程的名字
					switch (Thread.currentThread().getName()) {
					case "线程1":
						this.count1++;
						break;
					case "线程2":
						this.count2++;
						break;
					case "线程3":
						this.count3++;
						break;
					case "线程4":
						this.count4++;
						break;
					}
				} else {
					//这4个线程无论怎么样都会经过这里的,所以为了只输出一次,必须设置一个布尔值
					//这个isNotRead某种程度上也是一个信号量
					if (isNotRead) {
						System.out.println("^_^票已卖光,明天请早,都各自散吧!(杀死所有进程)");
						System.out.println("=========得票统计=========");
						System.out.println("线程1:" + count1 + "张");
						System.out.println("线程2:" + count2 + "张");
						System.out.println("线程3:" + count3 + "张");
						System.out.println("线程4:" + count4 + "张");
						isNotRead = false;
					}
					//这段与Thread.currentThread.stop()等价,eclipse在JDK1.7中推荐这样写
					Thread.yield();
				}
			}
			//临界区结束
		}
	}
}

因此,整个进程如下:

class GetTicket implements Runnable {
	//ticket是票数、isNotRead的设置是为了下面对于统计的输出只输出一次
	private int ticket = 200000;
	private boolean isNotRead = true;
	private int count1 = 0;
	private int count2 = 0;
	private int count3 = 0;
	private int count4 = 0;

	public void run() {
		while (this.ticket > 0) {
			//临界区开始,所谓的临界区就是仅能有一个线程访问的部分
			synchronized (this) {
				if (this.ticket > 0) {
					//对票的操作
					this.ticket--;
					System.out.println("票" + (200000 - this.ticket) + "被"
							+ Thread.currentThread().getName() + "买走,当前票数剩余:"
							+ this.ticket);
					//Thread.currentThread().getName()取当前线程的名字
					switch (Thread.currentThread().getName()) {
					case "线程1":
						this.count1++;
						break;
					case "线程2":
						this.count2++;
						break;
					case "线程3":
						this.count3++;
						break;
					case "线程4":
						this.count4++;
						break;
					}
				} else {
					//这4个线程无论怎么样都会经过这里的,所以为了只输出一次,必须设置一个布尔值
					//这个isNotRead某种程度上也是一个信号量
					if (isNotRead) {
						System.out.println("^_^票已卖光,明天请早,都各自散吧!(杀死所有进程)");
						System.out.println("=========得票统计=========");
						System.out.println("线程1:" + count1 + "张");
						System.out.println("线程2:" + count2 + "张");
						System.out.println("线程3:" + count3 + "张");
						System.out.println("线程4:" + count4 + "张");
						isNotRead = false;
					}
					//这段与Thread.currentThread.stop()等价,eclipse在JDK1.7中推荐这样写
					Thread.yield();
				}
			}
			//临界区结束
		}
	}
}

public class Threadtest {
	public static void main(String[] args) throws Exception {
		GetTicket getTicket = new GetTicket();
		new Thread(getTicket, "线程1").start();
		new Thread(getTicket, "线程2").start();
		new Thread(getTicket, "线程3").start();
		new Thread(getTicket, "线程4").start();
	}
}

好了,既然临界区都怎么写,那么之后的生产者、消费者问题、读者写者问题、理发师问题、哲学家进餐问题,还是问题吗?就是来秀逗、开玩笑、送分的!都是一个道理,就是来看你,怎么处理好多个进程对同一资源提出请求时,你怎么安排好,避免出现以下的情况:

【Java】利用synchronized(this)完成线程的临界区_第8张图片


你可能感兴趣的:(线程,操作系统,synchronized,临界区,临界资源)