阿里面试题-多线程按序打印

作者: 唠嗑008 | 来源:发表于2020-05-28 16:40 被阅读0次

    背景

    有朋友最近参加了阿里的面试,被问了一道线程同步的问题。偷偷跟你们说一下,阿里一面的最后都会问一道算法题,难度,LeetCode Easy级别。

    题目

    有一个Runnable任务,里面就2条语句,先打印"Hello",再打印"wold",现在创建5个线程去执行Runnable,要求先打印5个"Hello",再打印5个"wold"。

    很明显,这是一道线程同步问题,考察了线程生命周期,线程相关基础方法,sleep()、awit()等,线程锁这些知识。下面用多种方案来实现。

    一、简单粗暴法

    class MyRunnable(var tName: String) : Runnable {
        override fun run() {
            func1()
        }
    
        /**
         * 暴力解法,并发执行,在中间休眠
         */
        private fun func1() {
            println("$tName hello")
            Thread.sleep(2000)
            println("$tName wold")
        }
    }
    
    fun main() {
        for (index in 1..5) {
            Thread(MyRunnable("Thread $index")).start()
        }
    }
    

    二、使用synchronized同步锁

    每次线程打印“hello”先加锁,然后每次线程打印“hello”之后计数器加1,前4个线程打印“hello”之后会调用wait()来阻塞线程,等到第5个线程打印“hello”之后再唤醒所有线程。注意:kotlin没有Object的对象,kotlin所有类的父类是Any,但是Any没有实现wait()、notify()方法,所以需要创建一个java的Object对象。

    private var count = 0
    private var lock = Object()
    
    class MyRunnable(var tName: String) : Runnable {
        override fun run() {
            printHello()
            println("$tName wold")
        }
    
    
       private fun printHello() {
            synchronized(lock) {
                println("$tName hello")
                if (++count < 5) {
                    lock.wait()
                } else {
                    lock.notifyAll()
                }
            }
        }
    }
    
    fun main() {
        for (index in 1..5) {
            Thread(MyRunnable("Thread $index")).start()
        }
    }
    

    三、CyclicBarrier

    利用了juc并发包下的CyclicBarrier类,它里面有个方法await()可以让线程执行到这里就等待其他线程执行,等所有线程都到达后,会释放锁,放行所有线程。CyclicBarrier内部使用了ReentrantLock锁,其实自己也可以利用ReentrantLock来实现。

    private var cyclicBarrier = CyclicBarrier(5)
    
    class MyRunnable(var tName: String) : Runnable {
        override fun run() {
            cyclicBarrier()
        }
    
    
      /**
         * CyclicBarrier可以使一定数量的线程反复地在栅栏位置处汇集。当线程到达栅栏位置时将调用await方法,
         * 这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,
         * 而栅栏将被重置以便下次使用。
         */
        private fun cyclicBarrier() {
            println("$tName hello")
            // 线程打印完hello后都在这里等待
            cyclicBarrier.await()
            //所有线程都到达后,会释放锁,放行所有线程
            println("$tName wold")
        }
    }
    
    fun main() {
        for (index in 1..5) {
            Thread(MyRunnable("Thread $index")).start()
        }
    }
    

    四、CountDownLatch

    CountDownLatch也是juc并发包下的一个工具类,它允许一个或多个线程一直等待直到其他线程执行完毕才开始执行。用给定的计数初始化CountDownLatch,其含义是要被等待执行完的线程个数。每次调用ountDown(),计数减1,执行await()函数会阻塞线程的执行,直到计数为0。

    private var countDownLatch = CountDownLatch(5)
    
    class MyRunnable(var tName: String) : Runnable {
        override fun run() {
            countDownLatch()
        }
    
    
       /**
         * CountDownLatch是一个同步辅助类,它允许一个或多个线程一直等待直到其他线程执行完毕才开始执行。
         * 用给定的计数初始化CountDownLatch,其含义是要被等待执行完的线程个数。
         * 每次调用CountDown(),计数减1,主程序执行到await()函数会阻塞等待线程的执行,直到计数为0
         *
         * 原理:计数器通过使用锁(共享锁、排它锁)实现
         */
        private fun countDownLatch() {
            try {
                println("$tName hello")
            } finally {
                //计数器减1,初始值为5
                countDownLatch.countDown()
                if (countDownLatch.count > 0) {
                    //只要计算器>0,就阻塞线程
                    countDownLatch.await()
                }
            }
            println("$tName wold")
        }
    }
    
    fun main() {
        for (index in 1..5) {
            Thread(MyRunnable("Thread $index")).start()
        }
    }
    

    如果有更好的建议或方法,欢迎评论区留言交流。

    感谢

    Java并发编程之CyclicBarrier详解

    相关文章

      网友评论

        本文标题:阿里面试题-多线程按序打印

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