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

简介: 题目摘自:偏头痛杨最近看了这位博主的文章 写的挺好的 跟着里面的线程 温习了一遍 结尾处有道题算是复习巩固吧 我是用ReentrantLock实现的 而不是synchronized题目:使用3个线程,要求三个线程顺序执行,不允许使用sleep()强制让线程有顺序。

题目摘自:偏头痛杨

最近看了这位博主的文章 写的挺好的 跟着里面的线程 温习了一遍 结尾处有道题算是复习巩固吧
我是用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();


    }

}

实验结果:
这里写图片描述

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

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

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

目录
相关文章
|
3月前
|
Java 开发者
解锁并发编程新姿势!深度揭秘AQS独占锁&ReentrantLock重入锁奥秘,Condition条件变量让你玩转线程协作,秒变并发大神!
【8月更文挑战第4天】AQS是Java并发编程的核心框架,为锁和同步器提供基础结构。ReentrantLock基于AQS实现可重入互斥锁,比`synchronized`更灵活,支持可中断锁获取及超时控制。通过维护计数器实现锁的重入性。Condition接口允许ReentrantLock创建多个条件变量,支持细粒度线程协作,超越了传统`wait`/`notify`机制,助力开发者构建高效可靠的并发应用。
87 0
|
30天前
|
缓存 负载均衡 Java
c++写高性能的任务流线程池(万字详解!)
本文介绍了一种高性能的任务流线程池设计,涵盖多种优化机制。首先介绍了Work Steal机制,通过任务偷窃提高资源利用率。接着讨论了优先级任务,使不同优先级的任务得到合理调度。然后提出了缓存机制,通过环形缓存队列提升程序负载能力。Local Thread机制则通过预先创建线程减少创建和销毁线程的开销。Lock Free机制进一步减少了锁的竞争。容量动态调整机制根据任务负载动态调整线程数量。批量处理机制提高了任务处理效率。此外,还介绍了负载均衡、避免等待、预测优化、减少复制等策略。最后,任务组的设计便于管理和复用多任务。整体设计旨在提升线程池的性能和稳定性。
67 5
|
3月前
|
Java
【多线程面试题十六】、谈谈ReentrantLock的实现原理
这篇文章解释了`ReentrantLock`的实现原理,它基于Java中的`AbstractQueuedSynchronizer`(AQS)构建,通过重写AQS的`tryAcquire`和`tryRelease`方法来实现锁的获取与释放,并详细描述了AQS内部的同步队列和条件队列以及独占模式的工作原理。
【多线程面试题十六】、谈谈ReentrantLock的实现原理
|
3月前
|
前端开发 JavaScript 大数据
React与Web Workers:开启前端多线程时代的钥匙——深入探索计算密集型任务的优化策略与最佳实践
【8月更文挑战第31天】随着Web应用复杂性的提升,单线程JavaScript已难以胜任高计算量任务。Web Workers通过多线程编程解决了这一问题,使耗时任务独立运行而不阻塞主线程。结合React的组件化与虚拟DOM优势,可将大数据处理等任务交由Web Workers完成,确保UI流畅。最佳实践包括定义清晰接口、加强错误处理及合理评估任务特性。这一结合不仅提升了用户体验,更为前端开发带来多线程时代的全新可能。
64 1
|
3月前
|
Java 开发者
Java多线程教程:使用ReentrantLock实现高级锁功能
Java多线程教程:使用ReentrantLock实现高级锁功能
40 1
|
3月前
|
存储 监控 Java
|
3月前
|
Java C++
【Java 并发秘籍】synchronized vs ReentrantLock:揭秘线程同步神器的对决!
【8月更文挑战第24天】本文详细对比了Java并发编程中`synchronized`关键字与`ReentrantLock`的不同之处。`synchronized`作为内置关键字,提供自动锁管理但不支持中断或公平锁;`ReentrantLock`则通过显式调用方法控制锁,具备更多高级功能如可中断、公平锁及条件变量。文章通过两个计数器类实例展示了两种机制的具体应用,帮助读者理解其差异及适用场景。掌握这两者对于提升多线程程序设计能力至关重要。
55 0
|
4月前
多线程线程安全问题之synchronized和ReentrantLock在锁的释放上有何不同
多线程线程安全问题之synchronized和ReentrantLock在锁的释放上有何不同
|
4月前
|
Java Linux
Java演进问题之1:1线程模型对于I/O密集型任务如何解决
Java演进问题之1:1线程模型对于I/O密集型任务如何解决
|
3月前
|
Cloud Native Java 调度
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决
项目环境测试问题之线程同步器会造成执行完任务的worker等待的情况如何解决