• 多线程面试题(三)


    题目:两个线程,第一个线程是从1到26,第二个线程是从A到一直到Z,然后要让这两个线程做到同时运行,交替输出,顺序打印A1B2C3…Z26

    方法一:LockSupport结合park()/unpark()

    package com.my.controller.question;
    
    import java.util.concurrent.locks.LockSupport;
    
    /**
     * 用LockSupport顺序打印A1B2C3....Z26
     */
    public class TestLockSupport {
        static Thread t1 = null, t2 = null;
        public static void main(String[] args) throws Exception {
            char[] aI = "1234567".toCharArray();
            char[] aC = "ABCDEFG".toCharArray();
            t1 = new Thread(() -> {
                for(char c : aC) {
                    System.out.print(c);
                    LockSupport.unpark(t2); //叫醒T2
                    LockSupport.park(); //T1阻塞
                }
            }, "t1");
    
            t2 = new Thread(() -> {
    
                for(char c : aI) {
                    LockSupport.park(); //t2阻塞
                    System.out.print(c);
                    LockSupport.unpark(t1); //叫醒t1
                }
            }, "t2");
            t1.start();
            t2.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    方法二:synchronized结合wait()/notify()

    package com.my.controller.question;
    
    /**
     * 使用synchronized配合wait()/notify()顺序打印A1B2C3....Z26
     */
    public class Test_sync_wait_notify {
        public static void main(String[] args) {
            final Object o = new Object();
    
            char[] aI = "1234567".toCharArray();
            char[] aC = "ABCDEFG".toCharArray();
    
            new Thread(()->{
                synchronized (o) {
                    for(char c : aC) {
                        System.out.print(c);
                        try {
                            o.notify();
                            o.wait(); //让出锁
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    o.notify(); //必须,否则无法停止程序
                }
    
            }, "t1").start();
    
            new Thread(()->{
                synchronized (o) {
                    for(char c : aI) {
                        System.out.print(c);
                        try {
                            o.notify();
                            o.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
    
                    o.notify();
                }
            }, "t2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    如果需要保证某个线程先运行,可以使用volatile修饰的布尔变量,如下所示,保证t2先运行,因为要先打印A

    package com.my.controller.question;
    
    /**
     * 使用synchronized_wait()_notify()配合布尔变量(volatile+布尔变量是为了保证某个线程先运行)
     */
    public class Test_sync_wait_notify2 {
        private static volatile boolean t2Started = false;
        //private static CountDownLatch latch = new C(1);
        public static void main(String[] args) {
            final Object o = new Object();
            char[] aI = "1234567".toCharArray();
            char[] aC = "ABCDEFG".toCharArray();
            new Thread(()->{
                //latch.await();
                synchronized (o) {
                    while(!t2Started) {
                        try {
                            o.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    for(char c : aI) {
                        System.out.print(c);
                        try {
                            o.notify();
                            o.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    o.notify();
                }
            }, "t1").start();
    
            new Thread(()->{
                synchronized (o) {
                    for(char c : aC) {
                        System.out.print(c);
                        //latch.countDown()
                        t2Started = true;
                        try {
                            o.notify();
                            o.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    o.notify();
                }
            }, "t2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    方法三:使用ReentrantLock,定义等待队列Condition,结合lock()/signal(),一个condition就相当于一个等待队列,定义几个condition就是几个等待队列,此案例有一个condition,所以是t1和t2公用的等待队列

    package com.my.controller.question;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 使用ReentrantLock,定义一个等待队列Condition,结合lock()/signal()
     */
    public class Test_lock_condition {
        public static void main(String[] args) {
            char[] charsI = "1234567".toCharArray();
            char[] charsC = "ABCDEFG".toCharArray();
            ReentrantLock lock = new ReentrantLock();
            Condition condition = lock.newCondition();
    
            new Thread(()->{
                try {
                    lock.lock();
                    for(char c : charsC) {
                        System.out.print(c);
                        condition.signal();
                        condition.await();
                    }
                    condition.signal();
    
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }, "t1").start();
    
            new Thread(()->{
                try {
                    lock.lock();
                    for(char c : charsI) {
                        System.out.print(c);
                        condition.signal();
                        condition.await();
                    }
                    condition.signal();
    
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }, "t2").start();
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    还可以定义两个condition等待队列,即t1和t2分别有自己的等待队列,避免了只有一个等待队列导致的不必要的通知

    package com.my.controller.question;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * 使用ReentrantLock,定义两个等待队列Condition,结合lock()/signal()
     */
    public class Test_lock_condition {
        public static void main(String[] args) {
            char[] charsI = "1234567".toCharArray();
            char[] charsC = "ABCDEFG".toCharArray();
            ReentrantLock lock = new ReentrantLock();
            Condition condition1 = lock.newCondition();
            Condition condition2 = lock.newCondition();
    
            new Thread(()->{
                try {
                    lock.lock();
                    for(char c : charsC) {
                        System.out.print(c);
                        // 唤醒condition2等待队列中的线程
                        condition2.signal();
                        // 在t1线程中调用condition1.await(),表示线程1到condition1中去等待
                        condition1.await();
                    }
                    condition2.signal();
    
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }, "t1").start();
    
            new Thread(()->{
                try {
                    lock.lock();
                    for(char c : charsI) {
                        System.out.print(c);
                        condition1.signal();
                        condition2.await();
                    }
                    condition1.signal();
    
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }, "t2").start();
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    方法四:自写一个cas自旋的方式

    package com.my.controller.question;
    
    public class Test_cas {
        enum ReadyToRun {T1, T2}
    	//思考为什么必须volatile(保证可见性)
        static volatile ReadyToRun r = ReadyToRun.T1; 
    
        public static void main(String[] args) {
    
            char[] aI = "1234567".toCharArray();
            char[] aC = "ABCDEFG".toCharArray();
    
            new Thread(() -> {
    
                for (char c : aC) {
                    // 如果r = ReadyToRun.T1,while判断条件不成立,继续向下执行
                    // 如果r = ReadyToRun.T2,while判断条件成立,会一直循环下去,相当于自旋
                    while (r != ReadyToRun.T1) {}
                    System.out.print(c);
                    r = ReadyToRun.T2;
                }
    
            }, "t1").start();
    
            new Thread(() -> {
    
                for (char c : aI) {
                    while (r != ReadyToRun.T2) {}
                    System.out.print(c);
                    r = ReadyToRun.T1;
                }
            }, "t2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    方法五:利用BlockingQueue的阻塞方法,put()/take(),定义2个容量为1的ArrayBlockingQueue或者LinkedBlockingQueue

    package com.my.controller.question;
    
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    
    /**
     * 利用BlockingQueue的阻塞方法,put()/take(),定义2个容量为1的ArrayBlockingQueue/LinkedBlockingQueue
     */
    public class Test_BlockingQueue {
        static BlockingQueue q1 = new ArrayBlockingQueue(1);
        static BlockingQueue q2 = new ArrayBlockingQueue(1);
    
        public static void main(String[] args) throws Exception {
            char[] aI = "1234567".toCharArray();
            char[] aC = "ABCDEFG".toCharArray();
    
            new Thread(() -> {
    
                for(char c : aC) {
                    System.out.print(c);
                    try {
                        q1.put("ok");
                        q2.take();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "t1").start();
    
            new Thread(() -> {
    
                for(char c : aI) {
                    try {
                        q1.take();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.print(c);
                    try {
                        q2.put("ok");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "t2").start();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    方法六:利用TransferQueue的特性,结合transfer()/take()

    package com.my.controller.question;
    
    import java.util.concurrent.LinkedTransferQueue;
    import java.util.concurrent.TransferQueue;
    
    /**
     * 利用TransferQueue的特性,结合transfer()/take()
     */
    public class Test_TransferQueue {
        public static void main(String[] args) {
            char[] aI = "1234567".toCharArray();
            char[] aC = "ABCDEFG".toCharArray();
    
            TransferQueue queue = new LinkedTransferQueue();
            new Thread(()->{
                try {
                    for (char c : aI) {
                        //take()不到,所以先阻塞,相当于等着取
                        System.out.print("#"+queue.take());
                        queue.transfer(c);
                    }
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "t1").start();
    
            new Thread(()->{
                try {
                    for (char c : aC) {
                        //t2中调用transfer,这个c只能t1取,同样t1调用了transfer,只能t2取
                        //如果t1未提前take,t2中调用的transfer(c)将阻塞,直到t1取走
                        queue.transfer(c);
                        System.out.print(queue.take());
                    }
    
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, "t2").start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    以上提供的解题方法不唯一,有更好的方法欢迎评论区分享共勉。

  • 相关阅读:
    Vue源码学习(十八):实现组件注册(一)Vue.component()和Vue.extend()
    RabbitMQ中Direct交换机的用法
    Java中类的方法重载和方法重写
    基于SpringBoot+Vue+uniapp的OA办公系统(源码+lw+部署文档+讲解等)
    2023高频前端面试题-HTML
    区块链侧链技术(0)——衍生知识补充
    2、配置git环境与项目创建
    ShareMouse for Mac(多台电脑鼠标键盘共享软件)
    在IDEA中使用Git
    DevSecOps,让速度和安全兼顾
  • 原文地址:https://blog.csdn.net/qq_36184384/article/details/126707983