在多线程中线程的执行顺序是依靠哪一个线程先得到到CUP的执行权谁就先执行,虽说能够经过线程的优先权进行设置,可是他只是获取CUP执行权的几率高点,可是也不必定必须先执行。在这种状况下如何保证线程按照必定的顺序进行执行,今天就来一个大总结,分别介绍一下几种方式。
1、经过Object的wait和notify
2、经过Condition的awiat和signal
3、经过一个阻塞队列
4、经过两个阻塞队列
5、经过SynchronousQueue
6、经过线程池的Callback回调
7、经过同步辅助类CountDownLatch
8、经过同步辅助类CyclicBarrierios
写一个测试了Test,加上main方法,在写一个内部类Man进行测试。main方法以下,他进行建立两个线程,传进去Runnable对象。面试
public static boolean flag = false; public static int num = 0; public static void main(String[] args) { Man man = new Man(); new Thread(() -> { man.getRunnable1(); }).start(); new Thread(() -> { man.getRunnable2(); }).start(); }
getRunnable1和getRunnable2分别表示两个须要执行的任务,在两个线程中进行,方法1用于数据的生产,方法二用于数据的获取,数据的初始值为num = 0,为了保证生产和获取平衡须要使用wait和notify方法,这两个方法的使用必须是要加锁的,所以使用synchronized进行加锁使用,为了演示这个效果,咱们加上一个sleep方法模拟处理时间,以下:缓存
public static class Man { public synchronized void getRunnable1() { for (int i = 0; i < 20; i++) { while (flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("生产出:" + (++num) + "个"); flag = true; notify(); } } public synchronized void getRunnable2() { for (int i = 0; i < 20; i++) { while (!flag) { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } //模拟加载时间 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("取出出:" + (num--) + "个"); System.out.println("------------------"); flag = false; notify(); } } }
分析它的加载流程,从方法1进行分析,因为flag的初始条件为false,因此方法1不进入等待,直接进行生产,生产完成成以后,更新flag的值为true,同时notify下一个方法2的wait方法,使其变为唤醒状态。这时候因为方法1加锁了,没法执行方法1其余部分,当方法1执行完毕,方法1才有可能执行,可是方法1的flag已经为true,进入到wait里面又处于阻塞状态,因此这时候只能执行方法2了。因为方法2被唤醒了,阻塞解除,接下来就获取数据,当获取完毕又再次让flag变为false,notify方法1解除阻塞,再次执行方法1,就这样不断的循环,保证了不一样线程的有序执行,直到程序终止。网络
运行效果以下:
多线程
上面第一个的实现是一个阻塞,一个等待的方式保证线程有序的执行,可是不能进行两个线程之间进行通讯,而接下来介绍的Condition就具有这样的功能。要获取Condition对象首先先得获取Lock对象,他是在jdk1.5以后增长的,比synchronized性能更好的一种锁机制。和上面的相似,拷贝一份代码,看看main方法:框架
public static boolean flag = false; public static int num = 0; public static void main(String[] args) { Man man = new Man(); new Thread(() -> { man.getRunnable1(); }).start(); new Thread(() -> { man.getRunnable2(); }).start(); }
状况和第一个实现方法分析一致,这里不重复了。主要看内部类Man中的方法1和方法2。先手建立锁对象,把synchronized改成使用Lock加锁,其次经过Lock建立Condition对象,替换掉Object类的wait方法为Condition的await方法,最后换掉notify方法为signal方法便可,执行原理和上面分析一致,代码以下:ide
public static class Man { public static ReentrantLock lock = new ReentrantLock(); public static Condition condition = lock.newCondition(); public void getRunnable1() { lock.lock(); try { for (int i = 0; i < 20; i++) { while (flag) { try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("生产出:" + (++num) + "个"); flag = true; condition.signal(); } } finally { lock.lock(); } } public void getRunnable2() { lock.lock(); try { for (int i = 0; i < 20; i++) { while (!flag) { try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("取出出:" + (num--) + "个"); System.out.println("------------------"); flag = false; condition.signal(); } } finally { lock.unlock(); } } }
执行结果以下:
性能
这是个人iOS开发交流群:519832104无论你是小白仍是大牛欢迎入驻,能够一块儿分享经验,讨论技术,共同窗习成长!
另附上一份各好友收集的大厂面试题,须要iOS开发学习资料、面试真题,进群便可自行下载!
学习
点击此处,当即与iOS大牛交流学习
上面的两个方法实现起来代码比较繁琐,若是经过阻塞队列来实现会更加简洁,这里采用经常使用的容量为64的ArrayBlockingQueue来实现。main方法以下:测试
public static void main(String[] args) { Man man = new Man(); new Thread(() -> { man.getRunnable1(); }).start(); new Thread(() -> { man.getRunnable2(); }).start(); }
主要来看Man中的方法1和方法2,方法1中生产数据,这里把生产的数据存进队列里面,同时方法2进行取数据,若是方法1放满了或者方法2取完了就会被阻塞住,等待方法1生产好了或者方法2取出了,而后再进行。代码以下:
public static class Man { ArrayBlockingQueue queue = new ArrayBlockingQueue<Integer>(64); public void getRunnable1() { for (int i = 0; i < 8; i++) { System.out.println("生产出:" + i + "个"); try { queue.put(i); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("---------------生产完毕-----------------"); } public void getRunnable2() { for (int i = 0; i < 8; i++) { try { int num = (int) queue.take(); System.out.println("取出出:" + num); } catch (InterruptedException e) { e.printStackTrace(); } } } }
很明显使用阻塞队列代码精炼了不少,在这还能够发现这个阻塞队列是具备缓存功能的,想不少Android中网络访问框架内部就是使用这个进行缓存的,例如Volley、Okhttp等等。
运行效果以下:
使用一个阻塞队列可以实现线程同步的功能,两个阻塞队列也能够实现线程同步。原理是ArrayBlockingQueue他是具备容量的,若是把他的容量定位1则意味着他只能放进去一个元素,第二个方进行就会就会被阻塞。按照这个原理进行来实现,定义两个容量为1的阻塞队列ArrayBlockingQueue,一个存放数据,另外一个用于控制次序。main方法和上面一致,主要来看看Man类中的两个方法:
static class Man { //数据的存放 ArrayBlockingQueue queue1 = new ArrayBlockingQueue<Integer>(1); //用于控制程序的执行 ArrayBlockingQueue queue2 = new ArrayBlockingQueue<Integer>(1); { try { //queue2放进去一个元素,getRunnable2阻塞 queue2.put(22222); } catch (InterruptedException e) { e.printStackTrace(); } } public void getRunnable1() { new Thread(() -> { for (int j = 0; j < 20; j++) { try { //queue1放进一个元素,getRunnable1阻塞 queue1.put(j); System.out.println("存放 线程名称:" + Thread.currentThread().getName() + "-数据为-" + j); } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } try { //queue2取出元素,getRunnable2进入 queue2.take(); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } public void getRunnable2() { new Thread(() -> { for (int j = 0; j < 20; j++) { try { //queue2放进一个元素,getRunnable2阻塞 queue2.put(22222); } catch (InterruptedException e) { e.printStackTrace(); } try { //queue1放进一个元素,getRunnable1进入 int i = (int) queue1.take(); System.out.println("获取 线程名称:" + Thread.currentThread().getName() + "-数据为-" + i); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }
再次提醒queue2用于控制程序的执行次序,并没有实际含义。最后看看运行效果,存一个、取一个很清晰,以下:
SynchronousQueue不一样于通常的数据等线程,而是线程等待数据,他是一个没有数据缓冲的BlockingQueue,生产者线程对其的插入操做put必须等待消费者的移除操做take,反过来也同样。经过这一特性来实现一个多线程同步问题的解决方案,代码以下:
/** * 使用阻塞队列SynchronousQueue * offer将数据插入队尾 * take取出数据,若是没有则阻塞,直到有数据在获取到 */ public static void test() { SynchronousQueue queue = new SynchronousQueue(); ExecutorService executorService = Executors.newSingleThreadExecutor(); executorService.execute(new Runnable() { @Override public void run() { try { Thread.sleep(5000); queue.offer(9); } catch (InterruptedException e) { e.printStackTrace(); } } }); try { int take = (int) queue.take(); System.out.println(take); } catch (InterruptedException e) { e.printStackTrace(); } }
子线程中进行设置数据,而主线程获取数据,若是子线程没执行完毕,子线程没有执行完毕主线程就会被阻塞住不能执行下一步。
在线程的建立中,有一种建立方法能够返回线程结果,就是callback,他能返回线程的执行结果,经过子线程返回的结果进而在主线程中进行操做,也是一种同步方法,这种同步在Android中特别适用,例如Android中的AsyncTask源码中任务的建立部分。代码以下:
private static void test() { ExecutorService executorService = Executors.newFixedThreadPool(5); Future<Boolean> submit = executorService.submit(new Callable<Boolean>() { @Override public Boolean call() throws Exception { return false; } }); try { if (submit.get()) { System.out.println(true); } else { System.out.println(false); } } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }
CountDownLatch是一个同步的辅助类,容许一个或多个线程,等待其余一组线程完成操做,再继续执行。他类其实是使用计数器的方式去控制的,在建立的时候传入一个int数值每当咱们调用countDownt()方法的时候就使得这个变量的值减1,而对于await()方法则去判断这个int的变量的值是否为0,是则表示全部的操做都已经完成,不然继续等待。能够理解成倒计时锁。
public class Test7 { public static void main(String[] args) { //启动两个线程,分别执行完毕以后再执行主线程 CountDownLatch countDownLatch = new CountDownLatch(2); //线程1执行 Thread thread1 = new Thread(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "线程执行完毕"); countDownLatch.countDown(); }); //线程2执行 Thread thread2 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "线程执行完毕"); countDownLatch.countDown(); }); thread1.start(); thread2.start(); try { countDownLatch.await(); } catch (InterruptedException e) { e.printStackTrace(); } //执行主线程 System.out.println("主线程执行完毕"); } }
结果以下:
CyclicBarrier是一个同步的辅助类,和上面的CountDownLatch比较相似,不一样的是他容许一组线程相互之间等待,达到一个共同点,再继续执行。可当作是个障碍,全部的线程必须到齐后才能一块儿经过这个障碍。
public class Test8 { public static void main(String[] args) { //启动两个线程,分别执行完毕以后再执行主线程 CyclicBarrier barrier = new CyclicBarrier(2, () -> { //执行主线程 System.out.println("主线程执行完毕"); }); //线程1执行 Thread thread1 = new Thread(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "线程执行完毕"); try { barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }); //线程2执行 Thread thread2 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "线程执行完毕"); try { barrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } }); thread1.start(); thread2.start(); } }
运行结果: