ReentrantLock实现 多线程顺序执行任务

时间:2022-03-24 12:36:23

题目摘自:偏头痛杨

最近看了这位博主的文章 写的挺好的 跟着里面的线程 温习了一遍 结尾处有道题算是复习巩固吧
我是用ReentrantLock实现的 而不是synchronized

题目:

使用3个线程,要求三个线程顺序执行,不允许使用sleep()强制让线程有顺序。
线程A输出1
线程B输出2
线程C输出3
线程A输出4
线程B输出5
以此类推,一直输出到1000为止。

题目是不是看着不难 一开始我也是这么觉得的
后来我发现自己错了 还错了很离谱…… 也许是自己思维不够灵活 花了一下午时间解决……
不过也算是初步掌握了 ReentrantLock的运用

讲一下思路吧

题目要求是ABC顺序依次输出结果

可线程是抢占式的,鬼会听你安排 一个一个输出哦 肯定都是抢着去占CPU

线程是并发式的   意思就是 看起来像是一起执行  实际是一次只能执行一次线程 

那怎么才可以让线程乖乖听话?

答:一个执行的时候 另外两个等待不就OK了

比如 A执行的时候BC等待 A执行完后唤醒B执行 B执行完后唤醒C执行 C执行完后唤醒A执行 
这样一直循环 就是ABCABC的执行顺序了

过程:

思路有了,那我们如何来实现呢?

一开始 脑子一蹦出来就是用synchronized来解决 后来想想不行啊

synchronized的唤醒方式有2种(学的不深 不知道是否还有其他方法)
1. notify() 随机唤醒一个线程
2. notifyAll() 唤醒全部线程

题目要求我们是按照顺序ABC来执行线程的 我滴个神啊 这tm不是断我路吗!
别急别急 还好有 ReentrantLock

题外话:

话说网上一直有争议 到底是ReentrantLock处理并发好还是synchronized好?
讲实话 我也很难说清楚 因为自己也是学的不精 不过大致可以这么理解:

在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,
其性能下降很严重,此时ReentrantLock是个不错的方案

回到刚才的问题:如何使用ReentrantLock来解决顺序执行的问题?

首先我们得创建ReentrantLock锁和Condition小黑屋

private ReentrantLock r = new ReentrantLock();  //创建锁
    private Condition c1 = r.newCondition();    //小黑屋C1
    private Condition c2 = r.newCondition();    //小黑屋C2
    private Condition c3 = r.newCondition();    //小黑屋C3

上面小黑屋什么意思呢?

其实小黑屋是我给它取得名字 因为它很符合盒子的形象:

A执行的时候让BC等待  B,C就被关到c2,c3这两间小黑屋里  没有人来开门 它们就永远都无法出来
(B,C等待唤醒) 

第一条拦路虎:如何防止抢占?

多线程的常见问题 :A线程执行的时候B抢占CPU A停止手头工作等待 这会产生脏数据等问题
所以得加锁呀

private ReentrantLock r = new ReentrantLock();          //创建了锁
 r.lock();   //添加锁
 //代码A
 r.unlock(); // 释放锁

上面意思就是说 这块代码A被加锁了 无法被其他线程抢占

注意:不只是针对同一块代码啊!具体看下面这个栗子:

 r.lock();   //添加锁
 //代码A
 r.unlock(); // 释放锁

 r.lock();   //添加锁
 //代码B
 r.unlock(); // 释放锁

当线程A执行一个类中的 代码A时 线程B不能执行代码B 虽然他们不是同一个代码块
因为他们来自一个锁r(ReentrantLock)

所谓人一心不可二用 这里也是这个道理。一个锁 只能锁一个代码块
所以 有两种情况不行:

1. 线程A执行代码A 线程B执行代码A
2. 线程A执行代码A 线程B执行代码B

第二条拦路虎:如何让B被关进小黑屋(等待)?

.await()函数派上用场了 这就是等待的意思 对应synchronized的wait()函数

线程执行 await() 等待后 当前位置(执行await())下面代码不会被执行
同时释放锁 如果被唤醒了 还是从之前被锁定的位置开始
也可以这么理解 :从哪里跌倒 ,从哪里爬起来

在B线程里执行 c2.await();即可
就是把B线程放进c2中等待唤醒

举个栗子:

线程B代码块{
 r.lock();   //添加锁
 c2.await();      //把B关进c2小黑屋 等待唤醒 
 r.unlock(); // 释放锁
 }
注意了哦! 当线程等待的时候 自动释放锁 很关键 得记住:当调用await()时默认调用unlock()

第三条拦路虎:如何实现ABC线程顺序(这才是关键)

这个时候我们需要一个变量 来指示 当前线程到底时A还是B
1代表A
2代表B
3代表C

public int flag = 1;
线程A代码块{
if (flag != 1) {
        c1.await();    //A关进小黑屋C1
         }
        c2.signal();   //唤醒小黑屋c2里的家伙(这里指B)
}
线程B代码块{
if (flag != 2) {
        c2.await();    //B关进小黑屋C2
         }
        c3.signal();   //唤醒小黑屋C3里的家伙(这里指C)
}
线程C代码块{
if (flag != 3) {
        c3.await();    //C关进小黑屋C3
         }
        c1.signal();   //唤醒小黑屋c1里的家伙(这里指A)
}

最后一条拦路虎:如何停止线程ABC

核心代码
举个栗子A:

if(i==1001) {
            c2.signal();     //唤醒B
            c3.signal();     //唤醒C
            r.unlock();      //释放锁 
        }

r.unlock();这个是关键 一定要写 不然到最后C线程无法停止
因为 虽然唤醒了C1小黑屋里的C
但是: r这个锁还锁着其他线程 所以线程C无法 执行被r锁住的代码 参考第一条拦路虎

还有哦! 我是用 静态变量 i来记录这个递增的数 因为是三个线程共享嘛

提一下哦 !因为ABC线程只执行一次 我是直接采用 匿名对象来实现(实际是有类名的 不过是借用了父类或者接口的类名) 这样子

使用ReentrantLock锁来实现代码:

package day12;

import java.sql.Time;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

class Printer3 {
    private ReentrantLock r = new ReentrantLock();
    private Condition c1 = r.newCondition();
    private Condition c2 = r.newCondition();
    private Condition c3 = r.newCondition();
    public static int i = 1;
    public int flag = 1;

    public void print1() throws InterruptedException {
        r.lock();
        if (flag != 1) {
            c1.await();
        }
        if(i==1001) {
            c2.signal();
            c3.signal();
            r.unlock();
            return;
        }
        System.out.println("线程A:" + i++);
        flag = 2;

        c2.signal();
        r.unlock(); // 释放锁
    }

    public void print2() throws InterruptedException {
        r.lock();
        if (flag != 2) {
            c2.await();
        }
        if(i==1001) {
            c1.signal();
            c3.signal();
            r.unlock();
            return;
        }
        System.out.println("线程B:" + i++);
        flag = 3;
        c3.signal();
        r.unlock();
    }

    public void print3() throws InterruptedException {
        r.lock();
        if (flag != 3) {
            c3.await();
        }
        if(i==1001) {   
            c1.signal();
            c2.signal();
            r.unlock();
            return;
        }
        System.out.println("线程C:" + i++);
        flag = 1;
        c1.signal();
        r.unlock();
    }
}

public class thread {

    public static void main(String[] args) {
        final Printer3 p = new Printer3();
        long startTime = System.currentTimeMillis();
        new Thread() {
            public void run() {
                while (true) {
                    try {       
                        if (p.i == 1001) {
                            System.out.println("线程A结束");
                            break;
                        }
                        p.print1();
                    } catch (InterruptedException e) {

                        e.printStackTrace();
                    }
                }
            }
        }.start();
        new Thread() {
            public void run() {
                while (true) {
                    try {               
                        if (p.i == 1001) {
                            System.out.println("线程B结束");

                            break;
                        }
                        p.print2();
                    } catch (InterruptedException e) {

                        e.printStackTrace();
                    }
                }
            }
        }.start();

        new Thread() {
            public void run() {
                long  startTime   = System.currentTimeMillis(); //程序结束记录时间
                while (true) {
                    try {
                        if (p.i == 1001) {
                            System.out.println("线程C结束");
                            break;
                        }
                        p.print3();


                    } catch (InterruptedException e) {

                        e.printStackTrace();
                    }
                }
                long  endTime = System.currentTimeMillis(); //程序结束记录时间
                long TotalTime = endTime - startTime; 
                System.out.print("耗时:"+TotalTime);
            }
        }.start();


    }

}

实验结果:
ReentrantLock实现 多线程顺序执行任务

最后提一下 线程 停止有多个方法 :
最好就是让他自己执行完run方法 自然消亡
而不是使用interrupt强制打断

真的是千辛万苦……不过 也算是有点收获了
要是我的这篇博客可以解答你的些许问题,那也是极好的

如果有问题 欢迎提出 一起学习 一起进步。