美文网首页Java大数据
上班摸鱼时引发的对多线程的思考

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

作者: Java弟中弟 | 来源:发表于2021-12-18 13:35 被阅读0次

    一、导读

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

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

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

    方便理解我们用下面这个例子来讲解!

    背景

    :我们需要先上班,才能摸鱼( 或者先玩王者荣耀,才知道自己菜 )

    翻译成 多线程 :一个线程负责上班,一个线程负责摸鱼,而这两个线程的调度是随机的,顺序由操作系统决定,我们怎么保证只有上班后,再开始摸鱼?

    Talk is cheap. Show me the code!

    二、具体实现

    文章会涉及到的方法:

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

    1、通过Join实现

    join是Thread类的方法,底层基于 wait+notify ,你可以把这个方法理解成插队,谁调用谁插队,具有局限性。

    适用于线程较少的场景,如果线程多了会造成无限套娃,有点麻烦,不够优雅。

    /**
     * 多线程练习
     *
     * @author JiaMing
     * @since 2021/12/15/0015 下午 12:00
     **/
    
    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实现

    /**
     * volatile练习
     *
     * @author JiaMing
     * @since 2021/12/15/0015 下午 14:21
     **/
    
    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类的通讯方法。

    /**
     * Synchronized练习
     *
     * @author JiaMing
     * @since 2021/12/15/0015 下午 14:37
     **/
    
    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类似。

    /**
     * ReentrantLock练习
     *
     * @author JiaMing
     * @since 2021/12/15/0015 下午 15:02
     **/
    
    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时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。

    /**
     * CountDownLatchL练习
     *
     * @author JiaMing
     * @since 2021/12/15/0015 下午 15:14
     **/
    
    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();
        }
    }
    

    以上就是关于多线程执行顺序的小练习,可以尝试去运行一下,有错误欢迎指出,共同进步!

    相关文章

      网友评论

        本文标题:上班摸鱼时引发的对多线程的思考

        本文链接:https://www.haomeiwen.com/subject/telgfrtx.html