【操作系统&java多线程技术】几种信号量机制算法

生产者消费者问题:

生产者消费者

  太经典了,就不多赘述了。

import java.util.concurrent.Semaphore;

public class ProducerAndConsumer {

	static int count = 0;

	private static final Semaphore full = new Semaphore(0);
	private static final Semaphore empty = new Semaphore(20);
	private static final Semaphore mutex = new Semaphore(1);

	public static void main(String[] args) {

		new Thread(()->{
			while (true) {
				try {
					Thread.sleep(100);
					empty.acquire();
					mutex.acquire();
					System.out.println("生产者生产了一个临界区资源,目前还有" + ++count + "个临界区资源。");
					mutex.release();
					full.release();
				} catch (InterruptedException e) {
					System.err.println("出现了某种状况导致线程不安全");
				}
			}
		}).start();

		new Thread(()->{
			while (true) {
				try {
					Thread.sleep(100);
					full.acquire();
					mutex.acquire();
					System.out.println("消费者消费了一个临界区资源,目前还有" + --count + "个临界区资源。");
					mutex.release();
					empty.release();
				} catch (InterruptedException e) {
					System.err.println("出现了某种状况导致线程不安全");
				}
			}
		}).start();

	}

}

多生产者消费者问题:

  一家四口人,爸爸妈妈女儿儿子在吃饭,桌上有一个盘子,每次只能由家长向其中放一个水果,爸爸专门放苹果,妈妈专门放橘子,儿子等着吃橘子,女儿等着吃苹果。

import java.util.concurrent.Semaphore;

/**
 * <b>同步关系:</b>
 * <li>父亲放了苹果之后女儿才能吃苹果</li>
 * <li>母亲放了橘子之后儿子才能吃橘子</li>
 * <li>只有盘子为空时,父母才能放水果</li>
 * <b>互斥关系:</b>
 * <li>同时只能有一个人从盘子拿东西或往盘子放东西</li>
 */
public class MultiProducerAndConsume {

	private static final Semaphore orange = new Semaphore(0);
	private static final Semaphore apple = new Semaphore(0);
	private static final Semaphore plate = new Semaphore(1);

	public static void main(String[] args) {

		new Thread(()->{
			while (true) {
				try {
					Thread.sleep(100);
					plate.acquire();
					System.out.println(Thread.currentThread().getName() + "把苹果放到了盘子中给女儿吃。");
					apple.release();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "父亲").start();

		new Thread(()->{
			while (true) {
				try {
					Thread.sleep(100);
					plate.acquire();
					System.out.println(Thread.currentThread().getName() + "把橘子放到了盘子中给儿子吃。");
					orange.release();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "母亲").start();

		new Thread(()->{
			while (true) {
				try {
					Thread.sleep(100);
					apple.acquire();
					System.out.println(Thread.currentThread().getName() + "吃掉了爸爸给的苹果。");
					plate.release();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "女儿").start();

		new Thread(()->{
			while (true) {
				try {
					Thread.sleep(100);
					orange.acquire();
					System.out.println(Thread.currentThread().getName() + "吃掉了妈妈给的橘子。");
					plate.release();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "儿子").start();

	}

}

  由于缓冲区大小为1,所以不需要mutex信号量也能保证互斥,不需要像上面那个一样设置mutex,但是如果是盘子里一次能放2个水果就必须得有一个mutex了,否则父亲对盘子p操作完后,盘子剩一个空,母亲也对盘子p操作,盘子没空了,然后父母同时放水果,他们有可能放的是盘子的统一个位置,也就是造成数据覆盖。

吸烟者问题:

吸烟者问题
  有三个手上没有烟的吸烟者,他们分别有:烟草、纸和胶水。他们想要抽烟就必须得得到另外两种材料。另外还有一个供应者,会免费的轮流供应三个吸烟者缺少的两种材料放在桌子(临界缓冲区)上供吸烟者自己制作香烟并吸烟。

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Semaphore;

/**
 * <b>同步关系:</b>
 * <li>桌上有组合一(纸 + 胶水)&rarr 第一个吸烟者吸烟</li>
 * <li>桌上有组合二(烟草 + 胶水) &rarr 第二个吸烟者吸烟</li>
 * <li>桌上有组合三(烟草 + 纸) &rarr 第三个吸烟者吸烟</li>
 * <li>吸烟者发出完成信号 &rarr 供应者将下一个组合放到桌子上</li>
 * <b>互斥关系:</b>
 * <li>三个吸烟者和供应者不能同时碰桌子(桌子是临界资源)</li>
 */
public class Smoker {

	static final Semaphore[] offer
			= new Semaphore[]{new Semaphore(0), new Semaphore(0), new Semaphore(0)};
	static final Semaphore finish = new Semaphore(0);

	static final Map<Integer, String> map = new HashMap<>();

	static {
		map.put(0, "纸和胶水");
		map.put(1, "烟草和胶水");
		map.put(2, "烟草和纸");
	}

	public static void main(String[] args) {

		new Thread(()->{
			int i  = 0;
			while (true) {
				try {
					offer[i].release();
					System.out.println(Thread.currentThread().getName() + "把" + map.get(i) + "放在了桌子上");
					i = (i +1) % 3;
					finish.acquire();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, "供应者").start();

		for (int i = 0; i < 3; i++) {
			int j = i;
			new Thread(()->{
				while (true) {
					try {
						offer[j].acquire();
						System.out.println(Thread.currentThread().getName() + "从桌子上拿走了" + map.get(j) + "并开始吸烟");
						finish.release();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}, "吸烟者" + j + "号").start();
		}

	}

}

读者-写者问题:

读者写者问题
  有读者和写者两组并发进程,共享一个文件,两个以上读进程同时访问文件不会产生副作用,但当某个写进程运行时,其他进程便不能运行了。

  因而有以下要求:① 允许多个读者可以同时对文件进行读取。② 只允许一个写者往文件中写入信息。③ 写者工作时其他进程不的访问临界区。

import java.io.*;
import java.util.concurrent.Semaphore;

/**
 * <b>互斥关系:</b>
 * <li>读者-读者不存在互斥关系,写者-写者存在互斥关系。</li>
 */
public class ReaderAndWriter {


	/* rw用来给文件资源上锁,w用来提高写进程的优先级,实现FCFS算法,避免饥饿。 */
	static Semaphore rw = new Semaphore(1);
	static Semaphore mutex = new Semaphore(1);
	static Semaphore w = new Semaphore(1);
	static int count = 0;
	static String path;

	public static void main(String[] args) {

		path = "text.txt";

		new Writer("Hello world!", "写者一").start();
		new Writer("你好世界!", "写者二").start();

		new Reader("读者一").start();
		new Reader("读者二").start();
		new Reader("读者三").start();
		new Reader("读者四").start();
		new Reader("读者五").start();
		new Reader("读者六").start();
		new Reader("读者七").start();

	}

	static class Writer extends Thread{

		private final char[] arr;

		synchronized void write(char c) {
			try(BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path, true)))) {
				writer.write(c);
				System.out.println(Thread.currentThread().getName() + "写下了" + c);
				writer.flush();
			} catch (IOException e) {
				System.err.println("写入时出现IO异常。");
			}
		}

		public Writer(String str, String name) {
			arr = str.toCharArray();
			this.setName(name);
		}

		@Override
		public void run() {
			for (int i = 0; i < arr.length; i++) {
				try {
					w.acquire();
					rw.acquire();
					write(arr[i]);
					rw.release();
					w.release();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}

	static class Reader extends Thread{

		synchronized void read() {
			try(BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(path)))) {
				System.out.println(Thread.currentThread().getName() + "读出了" + reader.readLine());
			} catch (IOException e) {
				System.err.println("读取时出线IO异常");
			}
		}

		public Reader(String name) {
			super();
			this.setName(name);
			this.setDaemon(true);
		}

		@Override
		public void run() {
			try {

				w.acquire();

				mutex.acquire();
					if (count == 0) {
						/* 这里加的锁防小人不防君子,也就是对写着来说我说我这个所有用,对读者来说你说我这个没用。 */
						rw.acquire();
					}
					count++;
				/* 从mutex的p操作到v操作的这一段代码必须得是原子操作,mutex它的作用其实就相当于是一个synchronize块 */
				mutex.release();

				w.release();

				read();

				mutex.acquire();
					count--;
					if (count == 0) {
						rw.release();
					}
				mutex.release();

			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}

哲学家就餐问题:

哲学家进餐问题
  哲学家就餐问题可以这样表述,假设有五位哲学家围坐在一张圆形餐桌旁,做以下两件事情之一:吃饭,或者思考。吃东西的时候,他们就停止思考,思考的时候也停止吃东西。餐桌中间有一大碗意大利面,每两个哲学家之间有一只餐叉。因为用一只餐叉很难吃到意大利面,所以假设哲学家必须用两只餐叉吃东西。他们只能使用自己左右手边的那两只餐叉。哲学家就餐问题有时也用米饭和筷子而不是意大利面和餐叉来描述,因为很明显,吃米饭必须用两根筷子。

  哲学家从来不交谈,这就很危险,可能产生死锁,每个哲学家都拿着左手的餐叉,永远都在等右边的餐叉(或者相反)。即使没有死锁,也有可能发生资源耗尽。例如,假设规定当哲学家等待另一只餐叉超过五分钟后就放下自己手里的那一只餐叉,并且再等五分钟后进行下一次尝试。这个策略消除了死锁(系统总会进入到下一个状态),但仍然有可能发生“活锁”。如果五位哲学家在完全相同的时刻进入餐厅,并同时拿起左边的餐叉,那么这些哲学家就会等待五分钟,同时放下手中的餐叉,再等五分钟,又同时拿起这些餐叉。

  首先我们先来看这样一串代码及其运行结果:

import java.util.concurrent.Semaphore;

public class MealOfPhilosopher {

	static Semaphore[] chopsticks = new Semaphore[5];

	static {
		for (int i = 0; i < 5; i++) {
			chopsticks[i] = new Semaphore(1);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			int j = i;
			new Thread(()->{
				while (true) {
					try {
						chopsticks[j].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");
						chopsticks[(j + 1) % 5].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了他右边的" + (j + 1) % 5 + "号筷子");
						System.out.println(Thread.currentThread().getName() + "正在吃饭。");
						chopsticks[j].release();
						System.out.println(Thread.currentThread().getName() + "放下了他左边的" + j + "号筷子");
						chopsticks[(j + 1) % 5].release();
						System.out.println(Thread.currentThread().getName() + "放下了他右边的" + (j + 1) % 5 + "号筷子");
						System.out.println(Thread.currentThread().getName() + "正在思考。");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}, "哲学家" + i + "号").start();
		}
	}

}

死锁
死锁
  很明显,死锁住了。我们要必然要避免死锁,如何避免呢?

  方案一:可以设置让最多只有四名哲学家同时拿筷子。如果出现四个哲学家都拿了左手边的筷子,第五个哲学家就不能再拿筷子了,不能拿筷子的哲学家左边或右边的哲学家就可以吃饭了,从而避免死锁现象。

import java.util.concurrent.Semaphore;

public class MealOfPhilosopher {

	static Semaphore[] chopsticks = new Semaphore[5];
	static Semaphore semaphore = new Semaphore(4);

	static {
		for (int i = 0; i < 5; i++) {
			chopsticks[i] = new Semaphore(1);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			int j = i;
			new Thread(()->{
				while (true) {
					try {
						semaphore.acquire();
						chopsticks[j].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");
						chopsticks[(j + 1) % 5].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了他右边的" + (j + 1) % 5 + "号筷子");
						System.out.println(Thread.currentThread().getName() + "正在吃饭。");
						chopsticks[j].release();
						System.out.println(Thread.currentThread().getName() + "放下了他左边的" + j + "号筷子");
						chopsticks[(j + 1) % 5].release();
						System.out.println(Thread.currentThread().getName() + "放下了他右边的" + (j + 1) % 5 + "号筷子");
						semaphore.release();
						System.out.println(Thread.currentThread().getName() + "正在思考。");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}, "哲学家" + i + "号").start();
		}
	}

}

  方案二:奇数号的哲学家优先拿做手边的筷子,偶数号的哲学家优先拿右手边的筷子。可以保证相邻的两个哲学家都想进餐时只会有其中一个可以拿起第一支筷子,另一个会直接阻塞。就避免了占有一只后再等待另外一只的情况。

import java.util.concurrent.Semaphore;

public class MealOfPhilosopher {

	static Semaphore[] chopsticks = new Semaphore[5];

	static {
		for (int i = 0; i < 5; i++) {
			chopsticks[i] = new Semaphore(1);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			int j = i;
			new Thread(()->{
				while (true) {
					try {

						int first = j % 2 == 1 ? j :(j + 1) % 5;
						int second = j % 2 == 0 ? j :(j + 1) % 5;

						chopsticks[first].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了" + j + "号筷子");
						chopsticks[second].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了" + (j + 1) % 5 + "号筷子");
						System.out.println(Thread.currentThread().getName() + "正在吃饭。");
						chopsticks[first].release();
						System.out.println(Thread.currentThread().getName() + "放下了" + j + "号筷子");
						chopsticks[second].release();
						System.out.println(Thread.currentThread().getName() + "放下了" + (j + 1) % 5 + "号筷子");
						System.out.println(Thread.currentThread().getName() + "正在思考。");

					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}, "哲学家" + i + "号").start();
		}
	}

}

  方案三:互斥进行,当某个哲学家拿筷子的时候其他哲学家不能拿,也就是说某个哲学家拿起左筷子的时候,对mutex进行p操作,导致切换到其他哲学家进程的时候,其他哲学家无法拿筷子。当拿了左边筷子的哲学家同时拿了右边的筷子之后才能够对mutex进行v操作解锁。

public class MealOfPhilosopher {

	static Semaphore[] chopsticks = new Semaphore[5];
	static Semaphore mutex = new Semaphore(1);

	static {
		for (int i = 0; i < 5; i++) {
			chopsticks[i] = new Semaphore(1);
		}
	}

	public static void main(String[] args) {
		for (int i = 0; i < 5; i++) {
			int j = i;
			new Thread(()->{
				while (true) {
					try {
						mutex.acquire();
						chopsticks[j].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了他左边的" + j + "号筷子");
						chopsticks[(j + 1) % 5].acquire();
						System.out.println(Thread.currentThread().getName() + "拿走了他右边的" + (j + 1) % 5 + "号筷子");
						mutex.release();
						System.out.println(Thread.currentThread().getName() + "正在吃饭。");
						chopsticks[j].release();
						System.out.println(Thread.currentThread().getName() + "放下了他左边的" + j + "号筷子");
						chopsticks[(j + 1) % 5].release();
						System.out.println(Thread.currentThread().getName() + "放下了他右边的" + (j + 1) % 5 + "号筷子");
						System.out.println(Thread.currentThread().getName() + "正在思考。");
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}, "哲学家" + i + "号").start();
		}
	}

}

  这种方案看起来好像会发生死锁,为什么呢,比如说一号哲学家拿起了左右筷子,然后二号准备拿,先给mutex上锁,然后发现左边的筷子已经被一号哲学家拿走了,他就会被阻塞,然后三号哲学家也会准备去拿筷子但是mutex被上锁了,三号哲学家也会被阻塞,即便三号哲学家左右手边的筷子明明都能用。但实际上这并不是死锁,当一号哲学家用完餐后会把筷子放回去,二号哲学家就可以停止阻塞状态,进行进餐,进餐结束后mutex就解锁了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

九死九歌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值