一, 什么是多线程?
引用网上别人的一段话:
单进程单线程: 一个人在一个桌子上吃菜.
单进程多线程: 多个人在同一个桌子上一起吃菜.
多进程单线程: 多个人每个人在自己的桌子上吃菜.
多线程的问题是多个人同时吃一道菜的时候容易发生争抢, 例如两个人同时夹一个菜, 一个人刚伸出筷子, 结果伸到的时候已经被夹走菜了. 资源共享就会发生冲突争抢.
使用多线程的优点 (相对使用多进程来说):
进程之间不能共享内存, 但线程之间共享内存非常容易.
系统创建线程所分配的资源相对创建进程而言, 代价非常小.
二, Java 中实现多线程的 3 种方法介绍和比较
继承 Thread 类
实现 Runnable 接口
实现 Callable 接口
这三种方法的介绍和比较 1, 实现 Runnable 接口相比继承 Thread 类有如下优势 1) 可以避免由于 Java 的单继承特性而带来的局限 2) 增强程序的健壮性, 代码能够被多个线程共享, 代码与数据是独立的 3) 适合多个相同程序代码的线程去处理同一资源的情况 4) 线程池只能放入实现 Runable 或 Callable 类线程, 不能直接放入继承 Thread 的类
2, 实现 Runnable 接口和实现 Callable 接口的区别 1)Runnable 是自从 java1.1 就有了, 而 Callable 是 1.5 之后才加上去的 2) 实现 Callable 接口的任务线程能返回执行结果, 而实现 Runnable 接口的任务线程不能返回结果 3)Callable 接口的 call() 方法允许抛出异常, 而 Runnable 接口的 run() 方法的异常只能在内部消化, 不能继续上抛 4) 加入线程池运行, Runnable 使用 ExecutorService 的 execute 方法, Callable 使用 submit 方法 注: Callable 接口支持返回执行结果, 此时需要调用 FutureTask.get() 方法实现, 此方法会阻塞主线程直到获取返回结果, 当不调用此方法时, 主线程不会阻塞
三, Runnable,Thread,Callable 案例
3.1, 第一种实现方法 - 继承 Thread 类
继承 Thread 类, 需要覆盖方法 run() 方法, 在创建 Thread 类的子类时需要重写 run(), 加入线程所要执行的代即可.
- package cn.huangt.java_learn_notes.multithread;
- /**
- * 继承 Thread 实现多线程
- * @author huangtao
- */
- public class ThreadExtends {
- public static void main(String[] args) {
- new MyThread("Thread 测试").start();
- new MyThread("Thread 测试").start();
- }
- }
- class MyThread extends Thread{
- private String acceptStr;
- public MyThread(String acceptStr) {
- this.acceptStr = acceptStr;
- }
- public void run() {
- for (int i = 0; i <5; i ++) {
- System.out.println("这个传给我的值:"+acceptStr+", 加上一个变量, 看看是什么效果:"+i);
- }
- }
- }
- /*
- 输出内容 ===
- 这个传给我的值: Thread 测试, 加上一个变量, 看看是什么效果: 0
- 这个传给我的值: Thread 测试, 加上一个变量, 看看是什么效果: 0
- 这个传给我的值: Thread 测试, 加上一个变量, 看看是什么效果: 1
- 这个传给我的值: Thread 测试, 加上一个变量, 看看是什么效果: 2
- 这个传给我的值: Thread 测试, 加上一个变量, 看看是什么效果: 3
- 这个传给我的值: Thread 测试, 加上一个变量, 看看是什么效果: 4
- 这个传给我的值: Thread 测试, 加上一个变量, 看看是什么效果: 1
- 这个传给我的值: Thread 测试, 加上一个变量, 看看是什么效果: 2
- 这个传给我的值: Thread 测试, 加上一个变量, 看看是什么效果: 3
- 这个传给我的值: Thread 测试, 加上一个变量, 看看是什么效果: 4
- */
3.2, 第二种实现方法 - 实现 Runnable 接口
如果要实现多继承就得要用 implements,Java 提供了接口 java.lang.Runnable 来解决上边的问题.
Runnable 是可以共享数据的, 多个 Thread 可以同时加载一个 Runnable, 当各自 Thread 获得 CPU 时间片的时候开始运行 Runnable,Runnable 里面的资源是被共享的, 所以使用 Runnable 更加的灵活. PS: 需要解决共享之后产生的资源竞争问题.
- package cn.huangt.java_learn_notes.multithread;
- /**
- * Runnable 接口实现多线程
- * @author huangtao
- */
- public class RunnableImpl implements Runnable {
- private String acceptStr;
- public RunnableImpl(String acceptStr) {
- this.acceptStr = acceptStr;
- }
- public void run() {
- try {
- // 线程阻塞 1 秒, 此时有异常产生, 只能在方法内部消化, 无法上抛
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- // 最终处理结果无法返回
- System.out.println("hello :" + this.acceptStr);
- }
- public static void main(String[] args) {
- Runnable runnable = new RunnableImpl("Runable 测试");
- long beginTime = System.currentTimeMillis();
- new Thread(runnable).start();
- long endTime = System.currentTimeMillis();
- // endTime 和 beginTime 是一样的, 线程并不会阻塞主线程
- System.out.println("cast :" + (endTime - beginTime) / 1000 + "second!");
- }
- }
- /*
- 输出内容 ===
- cast : 0 second!
- hello : Runable 测试
- */
3.3, 第三种 - 实现 Callable 接口
Runnable 是执行工作的独立任务, 但是它不返回任何值. 如果你希望任务在完成的能返回一个值, 那么可以实现 Callable 接口而不是 Runnable 接口. 在 Java SE5 中引入的 Callable 是一种具有类型参数的泛型, 它的参数类型表示的是从方法 call()(不是 run()) 中返回的值.
- package cn.huangt.java_learn_notes.multithread;
- import java.util.concurrent.Callable;
- import java.util.concurrent.FutureTask;
- /**
- * Callable 接口实现多线程
- * @author huangtao
- */
- public class CallableImpl implements Callable<String> {
- private String acceptStr;
- public CallableImpl(String acceptStr) {
- this.acceptStr = acceptStr;
- }
- public String call() throws Exception {
- // 任务阻塞 1 秒, 并且增加一些信息返回
- Thread.sleep(1000);
- return this.acceptStr + "增加一些字符并返回";
- }
- public static void main(String[] args) throws Exception {
- Callable<String> callable = new CallableImpl("Callable 测试");
- FutureTask<String> task = new FutureTask<String>(callable);
- // 创建线程
- new Thread(task).start();
- long beginTime = System.currentTimeMillis();
- // 调用 get() 阻塞主线程, 反之, 线程不会阻塞
- String result = task.get();
- long endTime = System.currentTimeMillis();
- System.out.println("hello :" + result);
- // endTime 和 beginTime 是不一样的, 因为阻塞了主线程
- System.out.println("cast :" + (endTime - beginTime) / 1000 + "second!");
- }
- }
- /*
- 输出内容 ===
- hello : Callable 测试 增加一些字符并返回
- cast : 1 second!
- */
四, Runnable,Thread,Callable 总结
最后再来看看它们三个之间的总结.
4.1, 实现 Runnable 接口相比继承 Thread 类有如下优势
1) 可以避免由于 Java 的单继承特性而带来的局限 2) 增强程序的健壮性, 代码能够被多个线程共享, 代码与数据是独立的 3) 适合多个相同程序代码的线程去处理同一资源的情况 4) 线程池只能放入实现 Runable 或 Callable 类线程, 不能直接放入继承 Thread 的类
4.2, 实现 Runnable 接口和实现 Callable 接口的区别
1)Runnable 是自从 java1.1 就有了, 而 Callable 是 1.5 之后才加上去的 2) 实现 Callable 接口的任务线程能返回执行结果, 而实现 Runnable 接口的任务线程不能返回结果 3)Callable 接口的 call() 方法允许抛出异常, 而 Runnable 接口的 run() 方法的异常只能在内部消化, 不能继续上抛 4) 加入线程池运行, Runnable 使用 ExecutorService 的 execute 方法, Callable 使用 submit 方法 注: Callable 接口支持返回执行结果, 此时需要调用 FutureTask.get() 方法实现, 此方法会阻塞主线程直到获取返回结果, 当不调用此方法时, 主线程不会阻塞
五, 其他
当然, 关于多线程, 只掌握这些肯定不够. 还有多线程的实现原理, 还有深入理解 Java 线程池, 这样才能更好地使用多线程. 我在后面的文章中会更新. 文章中的代码在我的 GitHub 上: https://github.com/huangtao1208/java_learn_notes
来源: https://juejin.im/entry/5b866dd251882542e606687f