面试官问我怎么实现多个线程顺序执行,我一连说5种方法!

前言

昨天晚上面试一家公司,问的问题都比较基础,就想考考基础扎不扎实。
其中有一个问题是老掉牙 的怎么实现多线程顺序执行~
首先要理解多线程的几个生命周期,当调用Thread的start()方法,不代表这个线程就立刻执行了,而是这个线程进入了就绪状态,要执行的话还需要由CPU调度才能进入运行状态,其余的状态就不多说了……

声明: 本文示例参照了

让线程按顺序执行8种方法

让多线程按顺序执行的几种方法

一、Thread的join()方法

当然,thread的join方法实现多线程顺序执行有两种方式,一种是在子线程内部调用join()方法,另一种是直接在主线程调用join()方法;写法上是主线程上调用join()更直观,谁先执行谁后执行一目了然。子线程内部调用的话,相对的要注意调用的顺序。

1.子线程内部调用join()

public class ThreadJoinDemo {
    public static void main(String[] args) throws InterruptedException {
        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("打开冰箱!");
            }
        });
 
        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    thread1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("拿出一瓶牛奶!");
            }
        });
 
        final Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    thread2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("关上冰箱!");
            }
        });
 
        //下面三行代码顺序可随意调整,程序运行结果不受影响,因为我们在子线程中通过“join()方法”已经指定了运行顺序(还有一个原因就是之前说的多线程的生命周期中有就绪状态,需要等待cpu调度,start并不意味着运行状态)。
        thread3.start();
        thread2.start();
        thread1.start();
    }
}

运行结果

打开冰箱!
拿出一瓶牛奶!
关上冰箱!

2.在主线程中通过join()方法指定顺序

public class ThreadMainJoinDemo {
    public static void main(String[] args) throws InterruptedException {
        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("打开冰箱!");
            }
        });
 
        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("拿出一瓶牛奶!");
            }
        });
 
        final Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("关上冰箱!");
            }
        });
 
        thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        thread3.start();
    }
}

运行结果

打开冰箱!
拿出一瓶牛奶!
关上冰箱!

注意: 这里得控制好顺序了,为什么要控制顺序呢,跟join()方法的源码有关,一起看看吧:

    public final void join() throws InterruptedException {
        join(0);
    }

查看Thread源码可知,join()方法内部调用了join(0);
进入如下方法,内部实现是调用了Object的wait(0),根据调用了join()方法的线程实例的存活状态,不断的循环判断是否让获得cpu资源的线程进入等待状态。

    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

通过join()方法的源码,其实可以自己去利用wait()实现如何利用wait()完成线程顺序执行,这里不赘述。

二、使用jdk5推出的线程池Executors创建单例的方法

public class ThreadPoolDemo {
 
   static ExecutorService executorService = Executors.newSingleThreadExecutor();
 
    public static void main(String[] args) {
        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("打开冰箱!");
            }
        });
 
        final Thread thread2 =new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("拿出一瓶牛奶!");
            }
        });
 
        final Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("关上冰箱!");
            }
        });
        executorService.submit(thread1);
        executorService.submit(thread2);
        executorService.submit(thread3);
        executorService.shutdown();        //使用完毕记得关闭线程池
    }
 
}

下面进入j.u.c包下的并发类,分水岭。。。

三、CountDownLatch(倒计数)类

CountDownLatch类的特点就是可以初始化设置一个state的值,每次调用countDown()方法进行减一,如果state没有减小到0就会被await()方法一直阻塞。利用CountDownLatch方法可以实现和join()方法子线程运用一样的妙处。当然,CountDownLatch类的缺点是state不能重置,只能初始化一次,意思就是减小到0后,就没用了。。。不能加回去。

public class ThreadCountDownLatchDemo {
 
    private static CountDownLatch countDownLatch1 = new CountDownLatch(1);
 
    private static CountDownLatch countDownLatch2 = new CountDownLatch(1);
 
    public static void main(String[] args) {
        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("打开冰箱!");
                countDownLatch1.countDown();
            }
        });
 
        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    countDownLatch1.await();
                    System.out.println("拿出一瓶牛奶!");
                    countDownLatch2.countDown();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
 
        final Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    countDownLatch2.await();
                    System.out.println("关上冰箱!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
 
        //下面三行代码顺序可随意调整,程序运行结果不受影响
        thread3.start();
        thread1.start();
        thread2.start();
    }
}

四、CyclicBarrier(回环栅栏)类

这个类对CountDownLatch不能重复计数进行了改进,实现了可重复计数,不用向上面例子那样创建多个CountDownLatch实例,这个只需创建一个实例,设置state的大小就可以了。
当然,CyclicBarrier放在这也是大材小用,CyclicBarrier一般的使用场景是控制一组线程同时开始执行,就像跑步比赛一样,当所有运动员准备就绪才可以开始跑。

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * @author wwj
 * 使用CyclicBarrier(回环栅栏)实现线程按顺序运行
 */
public class CyclicBarrierDemo {

    static CyclicBarrier barrier1 = new CyclicBarrier(2);
    static CyclicBarrier barrier2 = new CyclicBarrier(2);

    public static void main(String[] args) {

        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("产品经理规划新需求");
                    //放开栅栏1
                    barrier1.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        });

        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //放开栅栏1
                    barrier1.await();
                    System.out.println("开发人员开发新需求功能");
                    //放开栅栏2
                    barrier2.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        });

        final Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //放开栅栏2
                    barrier2.await();
                    System.out.println("测试人员测试新功能");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }
        });

        thread3.start();
        thread1.start();
        thread2.start();
    }
}
public class CyclicBarrierDemo {

    static class TaskThread extends Thread {
        
        CyclicBarrier barrier;
        
        public TaskThread(CyclicBarrier barrier) {
            this.barrier = barrier;
        }
        
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                System.out.println(getName() + " 到达栅栏 A");
                barrier.await();
                System.out.println(getName() + " 冲破栅栏 A");
                
                Thread.sleep(2000);
                System.out.println(getName() + " 到达栅栏 B");
                barrier.await();
                System.out.println(getName() + " 冲破栅栏 B");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    public static void main(String[] args) {
        int threadNum = 5;
        CyclicBarrier barrier = new CyclicBarrier(threadNum, new Runnable() {
            
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " 完成最后任务");
            }
        });
        
        for(int i = 0; i < threadNum; i++) {
            new TaskThread(barrier).start();
        }
    }
    
}

打印结果

Thread-1 到达栅栏 A
Thread-3 到达栅栏 A
Thread-0 到达栅栏 A
Thread-4 到达栅栏 A
Thread-2 到达栅栏 A
Thread-2 完成最后任务
Thread-2 冲破栅栏 A
Thread-1 冲破栅栏 A
Thread-3 冲破栅栏 A
Thread-4 冲破栅栏 A
Thread-0 冲破栅栏 A
Thread-4 到达栅栏 B
Thread-0 到达栅栏 B
Thread-3 到达栅栏 B
Thread-2 到达栅栏 B
Thread-1 到达栅栏 B
Thread-1 完成最后任务
Thread-1 冲破栅栏 B
Thread-0 冲破栅栏 B
Thread-4 冲破栅栏 B
Thread-2 冲破栅栏 B
Thread-3 冲破栅栏 B

五、Sephmore(信号量)类

Sephmore类它是类似于回环栅栏,但是又不大一样,它里面的计数值叫许可,可以初始化许可的值,每个线程实例可以获取指定的许可个数,当这个线程执行完毕可以归还许可;让其他线程继续去获取许可执行线程内容;当获取不到许可的线程就会阻塞住,等待其他线程执行完,释放了许可,才能够往下执行。

acquire():当前线程尝试去阻塞的获取1个许可证,此过程是阻塞的,当前线程获取了1个可用的许可证,则会停止等待,继续执行。

release():当前线程释放1个可用的许可证。

除了这两个方法外,Sephmore还有获取多个许可的方法,可以自行去查看源码。
当然,Sephmore放在这里用确实是大材小用了,它一般的使用场景都是类似于数据库连接池的并发控制,取完后归还一样。

import java.util.concurrent.Semaphore;
/**
 * 使用Sephmore(信号量)实现线程按顺序运行
 */
public class SemaphoreDemo {
    private static Semaphore semaphore1 = new Semaphore(1);
    private static Semaphore semaphore2 = new Semaphore(1);
    public static void main(String[] args) {
        final Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("产品经理规划新需求");
                semaphore1.release();
            }
        });

        final Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore1.acquire();
                    System.out.println("开发人员开发新需求功能");
                    semaphore2.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    semaphore2.acquire();
                    thread2.join();
                    semaphore2.release();
                    System.out.println("测试人员测试新功能");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        System.out.println("早上:");
        System.out.println("测试人员来上班了...");
        thread3.start();
        System.out.println("产品经理来上班了...");
        thread1.start();
        System.out.println("开发人员来上班了...");
        thread2.start();
    }
}

执行结果

早上:
测试人员来上班了...
产品经理来上班了...
开发人员来上班了...
产品经理规划新需求
开发人员开发新需求功能
测试人员测试新功能

六、其实还有很多

比如更高大上的AQS,锁,ReentrantLock配合Condition,涉及了两个队列,我比较头疼,一个是虚拟同步双向队列,一个是Condition的队列,只是我不太熟,理解不深刻,不敢拿出来卖,不过能使用后面三种基本上是对并发编程有一定理解了的。。。。。
有兴趣可以去看看,这个可重入锁弄懂可是很加分的

你可能感兴趣的:(个人理解)