目的
这一系列的博文的目的是帮助自己对多线程的知识做一个总结, 并且将 Java 中的多线程知识做一个梳理. 尽量做到全面和和简单易懂.
概念
进程与线程
进程是操作系统级别的, 进程是操作系统分配资源的基本单位, 一个进程可以包含多个线程, 线程共享进程的资源. 线程是 CPU 调度的基本单位.
为什么要使用多线程
多线程可以更好的利用多核 CPU 的性能, 多核 CPU 上跑多线程程序往往会比单线程更快, 有的时候甚至在单核 CPU 上多线程程序也会有更好的性能. 总结来说, 多线程可以更好的利用 CPU 资源. 但是多线程也会有上下文切换的性能损耗.
多线程的上下文切换
多线程的执行需要承担线程上下文切换所带来的性能损耗. 在多任务处理系统中, CPU 需要处理所有程序的操作, 当用户来回切换它们时, 需要记录这些程序执行到哪里. 上下文切换就是这样一个过程, 允许 CPU 记录并恢复各种正在运行的程序的状态, 使它们能够完成切换操作.
作业数往往大于 CPU 数, 那么一个 CPU 在某一个时刻只能执行一个任务, 那么如何让用户感觉好多任务在同时执行哪? 操作系统的设计这巧妙的利用了时间片轮转的方式, 每个任务只给一段运行时间, 然后保存运行状态, 加载下一个任务继续执行. 这样来回切换, 这个过程就叫做上下文切换. 时间片轮转的方式使一个 CPU 同时执行多个任务变成了可能 (归根结底, 任务还是串行, 一个一个执行的).
上面文中要保存的内容包括程序运行到哪个位置的信息等等 (这个保存在程序计数器中).
并行与并发
并发: 指的是同时运行多少个线程, 但是会有线程上下文切换的, 同一时刻只有一个线程运行.
并行: 针对现在的多核 CPU, 多个线程真正的同时运行. 同一时刻有多个线程同时运行.
多线程带来的风险
线程安全性问题.
我们的 Java 代码被编译成机器指令后, 可能需要多步才能完成, 由于 CPU 对线程进行切换, 会导致多步发生线程安全问题.
活跃性问题, 死锁 (哲学家就餐问题, 每个哲学家一支筷子), 饥饿 (有的线程永远得不锁, 得不到到执行), 活锁.
性能问题, 上下文切换.
线程
了解了多线程能够带给我们的好处和问题之后, 我们来看看线程的相关知识.
线程的生命周期
新建 (new): 创建后尚未启动的线程就处于这种状态.
运行 (Runnable): 线程正在运行的状态.
就绪 (Ready): 启动并调用 start 方法后就处于就绪状态, 等待 CPU 分配时间片后就可以运行.
阻塞 (Blocked): 可能是因为调用了 wait 方法进入阻塞 (释放锁), 也可能是 sleep 方法进入阻塞 (不释放锁).wait 方法获取锁后是 Ready 状态, 等待 CPU 分配时间片才能再次运行.
结束 (Terminated): 以终止线程的线程状态, 线程已经结束执行.
线程的创建
继承 Thread
定义一个线程类并继承 Thread 类, 重写里面的 run 方法. 因为 java 是单继承的, 所以并不推荐这种方式.
- public class Main {
- public static void main(String[] args) throws Exception {
- myThread th1 = new myThread();
- myThread th2 = new myThread();
- th1.start();
- th2.start();
- }
- }
- class myThread extends Thread {
- public void run() {
- for(int i = 0; i <5; i++) {
- System.out.println(Thread.currentThread().getName() + "运行 :" + i );
- }
- }
- }
实现 Runnable
定义一个线程类并实现 Runnable 接口, 使用的时候把这个 Runnable 实例传入到 Thread 类中即可.
- public class Main {
- public static void main(String[] args) throws Exception {
- myThread myth = new myThread();
- Thread th1 = new Thread(myth);
- Thread th2 = new Thread(myth);
- th1.start();
- th2.start();
- }
- }
- class myThread implements Runnable {
- public void run() {
- for(int i = 0; i < 5; i++) {
- System.out.println(Thread.currentThread().getName() + "运行 :" + i );
- }
- }
- }
实现 Callable 接口
创建线程类并实现 Callable 接口, 重写 call 方法, 这个方法会有返回值. 使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程.
- import java.util.concurrent.Callable;
- import java.util.concurrent.ExecutionException;
- import java.util.concurrent.FutureTask;
- public class Main {
- public static void main(String[] args) {
- ThreadDemo td = new ThreadDemo();
- // 1. 执行 Callable 方式, 需要 FutureTask 实现类的支持, 用于接收运算结果
- FutureTask<Integer> result = new FutureTask<>(td);
- new Thread(result).start();
- // 2. 接收线程运算后的结果
- Integer sum;
- try {
- // 等所有线程执行完, 获取值, 因此 FutureTask 可用于 闭锁
- sum = result.get();
- System.out.println("-----------------------------");
- System.out.println(sum);
- } catch (InterruptedException | ExecutionException e) {
- e.printStackTrace();
- }
- }
- }
- class ThreadDemo implements Callable<Integer> {
- @Override
- public Integer call() throws Exception {
- int sum = 0;
- for (int i = 0; i <= 10; i++) {
- System.out.println(i);
- sum += i;
- }
- return sum;
- }
- }
来源: http://www.bubuko.com/infodetail-3366706.html