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

上班摸鱼时引发的对多线程的思考,第1张

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

一、导读

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

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

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

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

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

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5671257.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-16

发表评论

登录后才能评论

评论列表(0条)

保存