4 线程间定制化通信
案例实现
案列:启动三个线程,按照如下要求:
AA打印5此,BB打印10次,CC打印15次,一共进行10轮
具体思路:
每个线程添加一个标志位,是该标志位则执行操作,并且修改为下一个标志位,通知下一个标志 位的线程
创建一个可重入锁 private Lock lock = new ReentrantLock();
分别创建三个开锁通知 private Condition c1 = lock.newCondition();
(他们能实现指定唤醒)
(注意)具体资源类中的A线程代码操作
上锁,(执行具体操作(判断、操作、通知),解锁)放于try、finally,具体代码如下
class Share{
private int flag = 1;
private Lock lock = new ReentrantLock();
// 创建三个Comdition对象,为了定向唤醒相乘
Condition c1 = lock.newCondition();
Condition c2 = lock.newCondition();
Condition c3 = lock.newCondition();
public void Aprint(int loop) {
//上锁
lock.lock();
try{
// 判断
while(flag!=1) {
c1.await();
}
// 干活
for (int i = 1; i <= 5; i++) {
System.out.println(Thread.currentThread().getName() + " ::本次第" + i + "次打印,是第" + loop+ "次循环");
}
flag = 2; //修改标志位,定向唤醒 线程b
// 唤醒
c2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
}
public void Bprint(int loop) {
//上锁
lock.lock();
try{
// 判断
while(flag!=2) {
c2.await();
}
// 干活
for (int i = 1; i <= 10; i++) {
System.out.println(Thread.currentThread().getName() + " ::本次第" + i + "次打印,是第" + loop+ "次循环");
}
flag = 3; //修改标志位,定向唤醒 线程b
// 唤醒
c3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
}
public void Cprint(int loop) {
//上锁
lock.lock();
try{
// 判断
while(flag!=3) {
c3.await();
}
// 干活
for (int i = 1; i <= 15; i++) {
System.out.println(Thread.currentThread().getName() + " ::本次第" + i + "次打印,是第" + loop+ "次循环");
}
flag = 1; //修改标志位,定向唤醒 线程b
// 唤醒
c1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 解锁
lock.unlock();
}
}
}
public class CustomInterThreadCommunication {
public static void main(String[] args) {
Share share = new Share();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
share.Aprint(i);
}
}
},"A").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
share.Bprint(i);
}
}
},"B").start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
share.Cprint(i);
}
}
},"C").start();
}
}
测试结果如下:
该案例需要注意
我们在学习操作系统中的同步可以知道,进程/线程同步有四个原则,都是为了禁止两个进程同时进入临界区。同步机制应该遵循以下原则
- 空闲让进:临界区空闲时,可以允许一个请求进入临界区的进程立即进入临界区
- 忙则等待:当已经有进程进入临界区的时候,其他试图进入临界区的进程必须等待
- 有限等待:对请求访问的进程,应保证能在有限时间内进入临界区
- 让权等待:当进程不能进入临界区的时候,应立即释放处理机,防止进程忙等待
很显然,该案例被称为单标志法。因为该案例设置一个公用整型变量flag,用于指示被允许进入临界区的进程编号。
若 flag =1,则允许 P 1 P_1 P1 进程进入临界区;若 flag =2,则允许 P 2 P_2 P2 进程进入临界区;若 flag =3,则允许 P 3 P_3 P3 进程进入临界区
该算法可确保每次只允许一个进程进入临界区。
但两个进程必须交替进入临界区,若某个进程不再进入临界区,则另一个进程也无法进入临界区
比如,若 P 3 P_3 P3 顺利进入临界区并从临界区离开,则此时临界区是空闲的,但 P 1 P_1 P1 并没有进入临界区的打算,flag = 1 一直成立, P 3 P_3 P3 就无法再次进入临界区。
违背了"空闲让进"原则,让资源利用不充分·
比如,将上述代码中的 main() 方法的C线程从10 改为 20 ,C线程不能访问 Share 资源了,因为 A 线程已经不再访问同时 flag 值不再改变了。
单标志法伪代码如下
//P_0进程
while(turn!=0);
critical section;
turn=1;
remainder section;
//P_1进程
while(turn!=1);
critical section;
turn=0;
remainder section;