栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

上班摸鱼时引发的对多线程的思考

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

上班摸鱼时引发的对多线程的思考

一、导读

最近看到一个需求,假如有个公众号,需要每天向订阅者推送消息,但是公众号需要获取最新的消息来进行推送,这就需要至少两个线程来完成,一个线程用来推送消息,一个用来获取最新消息。

那么问题来了——线程发消息要用到最新策略,所以必须让获取新消息的线程先执行完,而线程的调度是随机的,执行顺序由操作系统决定,我该怎么让推送消息线程在获取消息线程执行完了再执行?

这就要牵涉到线程间的通讯了,接下来的操作来帮助大家了解线程间通讯在实际项目中的运用有个大致了解!

方便理解我们用下面这个例子来讲解!
背景
:我们需要先上班,才能摸鱼(或者先玩王者荣耀,才知道自己菜 )
翻译成多线程:一个线程负责上班,一个线程负责摸鱼,而这两个线程的调度是随机的,顺序由操作系统决定,我们怎么保证只有上班后,再开始摸鱼?

Talk is cheap. Show me the code!


二、具体实现
文章会涉及到的方法:

  • 基于join
  • 基于volatile
  • 基于synchronized
  • 基于reentrantLock
  • 基于countDownLatch

1、通过Join实现

join是Thread类的方法,底层基于wait+notify,你可以把这个方法理解成插队,谁调用谁插队,具有局限性。
适用于线程较少的场景,如果线程多了会造成无限套娃,有点麻烦,不够优雅。


public class GoldbrickingTest {

    //用来记录摸鱼时长
    static int hour;

    public static void main(String[] args) {

        //线程1:用来上班
        Thread thread1 = new Thread(() -> {
            System.out.println("开始上班!");
        });

        //线程2:用来摸鱼
        Thread thread2 = new Thread(() -> {
            try {
                //让线程1插队,线程2执行到这儿时会被阻塞,直到线程1执行完
                thread1.join();
            } catch (Exception e) {
                e.printStackTrace();
            }
            for (hour = 1; hour < 9; hour = hour + 1) {
                System.out.println("开始摸鱼,已经摸了" + hour + "小时");
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //众所周知 摸鱼8小时就可以下班了!
                if (hour == 8) {
                    System.out.println("------------------>下班了!");
                   break;
                }
            }

        });
        //线程启动
        thread2.start();
        thread1.start();
    }
}


2、通过volatile实现


public class GoldbrickingTest02 {

    //定义一个共享变量用来线程间通信,用volatile修饰,保证它内存可见
    static volatile boolean flag = false;
    //用来记录摸鱼时长
    static int hour;
    public static void main(String[] args) {

        //线程1:用来上班
        Thread thread1 = new Thread(() -> {
            while (true) {
                if (!flag) {
                    System.out.println("开始上班!");
                    // 通知thread2你可以执行了
                    flag = true;
                    break;
                }
            }
        });

        //线程2:用来摸鱼
        Thread thread2 = new Thread(() -> {
            while (true) {
                if (flag) {
                    for (hour = 1; hour < 9; hour = hour + 1) {
                        System.out.println("开始摸鱼,已经摸了" + hour + "小时");
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //众所周知 摸鱼8小时就可以下班了!
                        if (hour == 8) {
                            System.out.println("------------------>下班了!");
                            break;
                        }
                    }
                    break;
                }
            }

        });
        //线程启动
        thread2.start();
        thread1.start();
    }
}

3、通过Synchronized实现
synchronized需要配合wait、notify方法一起使用,它俩都是Object类的通讯方法。


public class GoldbrickingTest03 {

    //用来记录摸鱼时长
    static int hour;

    public static void main(String[] args) {
        GoldbrickingTest03 goldbrickingTest03 = new GoldbrickingTest03();
        goldbrickingTest03.execute();
    }

    public void execute() {
        //线程1:用来上班
        Thread thread1 = new Thread(() -> {
            synchronized (this) {
                try {
                    System.out.println("开始上班!");
                    //唤醒等待中的thread2
                    notify();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

        //线程2:用来摸鱼
        Thread thread2 = new Thread(() -> {
            synchronized (this) {
                try {
                    //让线程等待
                    wait();
                    for (hour = 1; hour < 9; hour = hour + 1) {
                        System.out.println("开始摸鱼,已经摸了" + hour + "小时");
                        Thread.sleep(200);
                        //众所周知 摸鱼8小时就可以下班了!
                        if (hour == 8) {
                            System.out.println("------------------>下班了!");
                            break;
                        }
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread2.start();
        thread1.start();
    }
}


4、通过ReentrantLock实现
ReentrantLock是juc包下的并发工具,需结合Condition的await()和signal(),底层原理与上面的wait和notify类似。


public class GoldbrickingTest04 {

    //用来记录摸鱼时长
    static int hour;

    public static void main(String[] args) {
        //实例化一个锁和Condition
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        //线程1:用来上班
        Thread thread1 = new Thread(() -> {
            lock.lock();
            System.out.println("开始上班!");
            // 唤醒等待中的线程
            condition.signal();
            lock.unlock();
        });

        //线程2:用来摸鱼
        Thread thread2 = new Thread(() -> {
            lock.lock();
            try {
                //让线程等待
                condition.await();
                for (hour = 1; hour < 9; hour = hour + 1) {
                    System.out.println("开始摸鱼,已经摸了" + hour + "小时");
                    Thread.sleep(100);
                    //众所周知 摸鱼8小时就可以下班了!
                    if (hour == 8) {
                        System.out.println("------------------>下班了!");
                        break;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });
        //线程启动
        thread2.start();
        thread1.start();
    }
}

5、通过CountDownLatch实现
这也是juc包下的并发工具,主要有两个常用方法,countDown和await
原理:countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。


public class GoldbrickingTest05 {
    //用来记录摸鱼时长
    static int hour;

    public static void main(String[] args) {
        //实例化一个CountDownLatch,count设置为1,也就是说,只要调用一次countDown方法就会唤醒线程
        CountDownLatch countDownLatch = new CountDownLatch(1);

        //线程1:用来上班
        Thread thread1 = new Thread(() -> {
            //计数器减一
            countDownLatch.countDown();
            System.out.println("开始上班!");
        });

        //线程2:用来摸鱼
        Thread thread2 = new Thread(() -> {
            try {
                //阻塞当前线程,计数器为0时被唤醒
                countDownLatch.await();
                for (hour = 1; hour < 9; hour = hour + 1) {
                    System.out.println("开始摸鱼,已经摸了" + hour + "小时");
                    Thread.sleep(100);
                    //众所周知 摸鱼8小时就可以下班了!
                    if (hour == 8) {
                        System.out.println("------------------>下班了!");
                        break;
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
        //线程启动
        thread1.start();
        thread2.start();
    }
}


以上就是关于多线程执行顺序的小练习,可以尝试去运行一下,有错误欢迎指出,共同进步!
好文章 记得 收藏+点赞+关注 !!!

推荐阅读:

  • 由银行填表时返回上一步引发的对回溯算法的思考(java实现回溯算法)
  • 通过停车场计算车费案例练习JavaIO流

参考文章:https://blog.csdn.net/qq_33709582/article/details/121900989

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/665991.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号