赞
踩
昨天晚上面试一家公司,问的问题都比较基础,就想考考基础扎不扎实。
其中有一个问题是老掉牙 的怎么实现多线程顺序执行~
首先要理解多线程的几个生命周期,当调用Thread的start()方法,不代表这个线程就立刻执行了,而是这个线程进入了就绪状态,要执行的话还需要由CPU调度才能进入运行状态,其余的状态就不多说了……
声明: 本文示例参照了
当然,thread的join方法实现多线程顺序执行有两种方式,一种是在子线程内部调用join()方法,另一种是直接在主线程调用join()方法;写法上是主线程上调用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(); } }
运行结果
打开冰箱!
拿出一瓶牛奶!
关上冰箱!
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()完成线程顺序执行,这里不赘述。
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类的特点就是可以初始化设置一个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(); } }
这个类对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类它是类似于回环栅栏,但是又不大一样,它里面的计数值叫许可,可以初始化许可的值,每个线程实例可以获取指定的许可个数,当这个线程执行完毕可以归还许可;让其他线程继续去获取许可执行线程内容;当获取不到许可的线程就会阻塞住,等待其他线程执行完,释放了许可,才能够往下执行。
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的队列,只是我不太熟,理解不深刻,不敢拿出来卖,不过能使用后面三种基本上是对并发编程有一定理解了的。。。。。
有兴趣可以去看看,这个可重入锁弄懂可是很加分的
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。