案列:启动三个线程,按照如下要求:
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;