在并发编程中,最基本的就是建立线程了,那么通常的建立姿式是怎样的,又都有些什么区别java
通常来说线程建立有四种方式:编程
因此本篇博文从布局来说,分为两部分并发
目标: 建立两个线程并发实现从1-1000的累加框架
实现逻辑以下ide
public class AddThread extends Thread { private int start, end; private int sum = 0; public AddThread(String name, int start, int end) { super(name); this.start = start; this.end = end; } public void run() { System.out.println("Thread-" + getName() + " 开始执行!"); for (int i = start; i <= end; i ++) { sum += i; } System.out.println("Thread-" + getName() + " 执行完毕! sum=" + sum); } public static void main(String[] args) throws InterruptedException { int start = 0, mid = 500, end = 1000; AddThread thread1 = new AddThread("线程1", start, mid); AddThread thread2 = new AddThread("线程2", mid + 1, end); thread1.start(); thread2.start(); // 确保两个线程执行完毕 thread1.join(); thread2.join(); int sum = thread1.sum + thread2.sum; System.out.println("ans: " + sum); } }
输出结果布局
Thread-线程1 开始执行! Thread-线程2 开始执行! Thread-线程1 执行完毕! sum=125250 Thread-线程2 执行完毕! sum=375250 ans: 500500
通常实现步骤:学习
Thread
类run()
方法Thread#start()
执行逻辑比较清晰,只须要注意覆盖的是run方法,而不是start方法this
public class AddRun implements Runnable { private int start, end; private int sum = 0; public AddRun(int start, int end) { this.start = start; this.end = end; } @Override public void run() { System.out.println(Thread.currentThread().getName() + " 开始执行!"); for(int i = start; i <= end; i++) { sum += i; } System.out.println(Thread.currentThread().getName() + " 执行完毕! sum=" + sum); } public static void main(String[] args) throws InterruptedException { int start = 0, mid = 500, end = 1000; AddRun run1 = new AddRun(start, mid); AddRun run2 = new AddRun(mid + 1, end); Thread thread1 = new Thread(run1, "线程1"); Thread thread2 = new Thread(run2, "线程2"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); int sum = run1.sum + run2.sum; System.out.println("ans: " + sum); } }
输出结果线程
线程2 开始执行! 线程1 开始执行! 线程2 执行完毕! sum=375250 线程1 执行完毕! sum=125250 ans: 500500
通常实现步骤:code
Runnable
接口Thread#start()
启动线程说明
相比于继承Thread,这里是实现一个接口,最终依然是借助 Thread#start()
来启动线程
而后就有个疑问:
二者是否有本质上的区别,在实际项目中如何抉择?
Callable接口相比于Runnable接口而言,会有个返回值,那么如何利用这个返回值呢?
demo以下
public class AddCall implements Callable<Integer> { private int start, end; public AddCall(int start, int end) { this.start = start; this.end = end; } @Override public Integer call() throws Exception { int sum = 0; System.out.println(Thread.currentThread().getName() + " 开始执行!"); for (int i = start; i <= end; i++) { sum += i; } System.out.println(Thread.currentThread().getName() + " 执行完毕! sum=" + sum); return sum; } public static void main(String[] args) throws ExecutionException, InterruptedException { int start = 0, mid = 500, end = 1000; FutureTask<Integer> future1 = new FutureTask<>(new AddCall(start, mid)); FutureTask<Integer> future2 = new FutureTask<>(new AddCall(mid + 1, end)); Thread thread1 = new Thread(future1, "线程1"); Thread thread2 = new Thread(future2, "线程2"); thread1.start(); thread2.start(); int sum1 = future1.get(); int sum2 = future2.get(); System.out.println("ans: " + (sum1 + sum2)); } }
输出结果
线程2 开始执行! 线程1 开始执行! 线程2 执行完毕! sum=375250 线程1 执行完毕! sum=125250 ans: 500500
通常实现步骤:
Callable
接口Callable
的实现类为参数,建立FutureTask
实例FutureTask
做为Thread的参数,建立Thread实例Thread#start
启动线程FutreTask#get()
阻塞获取线程的返回值说明
Callable接口相比Runnable而言,会有结果返回,所以会由FutrueTask进行封装,以期待获取线程执行后的结果;
最终线程的启动都是依赖Thread#start
demo以下,建立固定大小的线程池,提交Callable任务,利用Future获取返回的值
public class AddPool implements Callable<Integer> { private int start, end; public AddPool(int start, int end) { this.start = start; this.end = end; } @Override public Integer call() throws Exception { int sum = 0; System.out.println(Thread.currentThread().getName() + " 开始执行!"); for (int i = start; i <= end; i++) { sum += i; } System.out.println(Thread.currentThread().getName() + " 执行完毕! sum=" + sum); return sum; } public static void main(String[] arg) throws ExecutionException, InterruptedException { int start=0, mid=500, end=1000; ExecutorService executorService = Executors.newFixedThreadPool(2); Future<Integer> future1 = executorService.submit(new AddPool(start, mid)); Future<Integer> future2 = executorService.submit(new AddPool(mid+1, end)); int sum = future1.get() + future2.get(); System.out.println("sum: " + sum); } }
输出
pool-1-thread-1 开始执行! pool-1-thread-2 开始执行! pool-1-thread-1 执行完毕! sum=125250 pool-1-thread-2 执行完毕! sum=375250 sum: 500500
通常实现逻辑:
Future#get
获取返回的结果上面虽说是有四种方式,但实际而言,主要划分为两类
此外,还有一种利用Fork/Join框架来实现并发的方式,后续专门说明,此处先略过
先把线程池的方式拎出来单独说,这里主要对比Thread, Callable, Runnable三中方式的区别
我的理解,线程的这两种方式的区别也就只有继承和实现接口的本质区别:
一个是继承Thread类,能够直接调用实例的 start()
方法来启动线程;另外一个是实现接口,须要借助 Thread#start()
来启动线程
继承由于java语言的限制,当你的任务须要继承一个自定义的类时,会有缺陷;而实现接口却没有这个限制
至于网上不少地方说的实现Runnable接口更利于资源共享什么的,好比下面这种做为对比的
public class ShareTest { private static class MyRun implements Runnable { private volatile AtomicInteger ato = new AtomicInteger(5); @Override public void run() { while (true) { int tmp = ato.decrementAndGet(); System.out.println(Thread.currentThread() + " : " + tmp); if (tmp <= 0) { break; } } } } public static void main(String[] args) throws InterruptedException { MyRun run = new MyRun(); Thread thread1 = new Thread(run, "线程1"); Thread thread2 = new Thread(run, "线程2"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("over"); } }
输出:
Thread[线程1,5,main] : 4 Thread[线程2,5,main] : 3 Thread[线程1,5,main] : 2 Thread[线程2,5,main] : 1 Thread[线程1,5,main] : 0 Thread[线程2,5,main] : -1 over
MyRun
实现Runnable
接口,而后建立一个实例,将这个实例做为多个Thread的参数构造Thread类,而后启动线程,发现这几个线程共享了 MyRun#ato
变量
然而上面这个实现接口改为继承Thread,其余都不变,也没啥两样
public class ShareTest { private static class MyRun extends Thread { private volatile AtomicInteger ato = new AtomicInteger(5); @Override public void run() { while (true) { int tmp = ato.decrementAndGet(); System.out.println(Thread.currentThread() + " : " + tmp); if (tmp <= 0) { break; } } } } public static void main(String[] args) throws InterruptedException { MyRun run = new MyRun(); Thread thread1 = new Thread(run, "线程1"); Thread thread2 = new Thread(run, "线程2"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("over"); } }
输出以下
Thread[线程1,5,main] : 4 Thread[线程2,5,main] : 3 Thread[线程1,5,main] : 2 Thread[线程1,5,main] : 0 Thread[线程2,5,main] : 1 Thread[线程2,5,main] : -1 over
上面除了说明使用Runnable更利于资源共享神马的,其实并无以外,还有一个比较有意思的,为何会输出-1?
若是我这个任务是售票的话,妥妥的就超卖了,这个问题留待后续详解
这两个就比较明显了,最根本的就是
从根源出发,就直接致使使用姿式上的区别
举个形象的例子说明两种方式的区别:
小明家今儿没米了,小明要吃饭怎么办?
小明他妈对小明说,去你大爷家吃饭吧,至于小明到底吃没吃着,小妈他妈就无论了,这就是Runnable方式;
小明他妈一想,这一家子都要吃饭,我先炒个菜,让小明去大爷家借点米来,因此就等着小明拿米回来开锅,这就是Callable方式
1.Runnable
Runnable不关心返回,因此任务本身默默的执行就能够了,也不用告诉我完成没有,我不care,您本身随便玩,因此通常使用就是
new Thread(new Runnable() { public void run() {...} }).start()
换成JDK8的 lambda表达式就更简单了 new Thread(() -> {}).start();
2.Callable
相比而言,callbale就悲催一点,无法这么随意了,由于要等待返回的结果,可是这个线程的状态我又控制不了,怎么办?借助FutrueTask
来玩,因此通常能够看到使用方式以下:
FutureTask<Object> future = new FutureTask<>(() -> null); new Thread(future).start(); Object obj = future.get(); // 这里会阻塞,直到线程返回值
这个就高端了,线程池一听就感受厉害了,前面的四中方式有三种都是Thread#start()
来启动线程,这也是咱们最经常使用的方式,这里单独说一下线程池的使用姿式
ExecutorService#submit()
提交线程Future<Object>
接收返回ExecutorService executorService = Executors.newFixedThreadPool(2); Future<Integer> future1 = executorService.submit(()-> 10); int ans = future1.get();
说明,这里提交线程以后,并不表示线程立马就要执行,也不表示必定能够执行(这个留待后续线程池的学习中探讨)
继承Thread类,覆盖run方法,调用 Thread#start
启动
实现Runnable接口,建立实例,做为Thread
构造参数传入,调用 Thread#start
启动
new Thread(() -> {}).start()
实现Callable接口,建立实例,做为FutureTask<>
构造参数建立FutureTask
对象,将FutureTask对象做为Thread
构造参数传入,调用 Thread#start
启动
FutureTask<Object> future = new FutureTask<>(() -> null); new Thread(future).start(); Object obj = future.get(); // 这里会阻塞,直到线程返回值
建立一个线程池,利用 ExecutorService#submit()
提交线程,Future<Object>
接收返回
ExecutorService executorService = Executors.newFixedThreadPool(2); Future<Integer> future1 = executorService.submit(()-> 10); int ans = future1.get();
尽信书则不如,已上内容,纯属一家之言,因本人能力通常,见识有限,若有问题,请不吝指正,感激