【并发编程】线程基础知识
线程基础知识
1. 线程与进程的区别
- 进程是正在运行程序的实例,进程中包含了线程,每个线程执行不同的任务;
- 不同的进程使用不同的内存空间,在当前进程下的所有线程可以共享内存空间;
- 线程更加轻量,线程上下文切换成本一般要比进程上下文切换低(上下文切换指的是一个线程/进程暂停,切换到另一个线程/进程执行,保存上一个线程/进程的上下文,加载下一个线程/进程的上下文);
- 上下文指的是一些执行的环境,状态,场景值;
2. 并行与并发的区别
在多核 CPU 下:
并发就是同一段时间,某个 CPU 内核轮流地(“同时”)执行多件事情;
并行就是同一时间点,多个 CPU 内核真的同时执行了多件事情,例如四核 CPU 就可以同时执行 4 个线程;
- 单核就没有并行了,只能说从宏观来看是并行;
3. 创建线程的方式有哪些?
- 重写 Thread 的 run 方法;
- 子类继承 Thread;
- 匿名内部类;
- 实现 Runnable 函数式接口的 run 方法;
- 子类实现 Runnable,传参给 Thread 的构造方法;
- 匿名内部类;
- lambda 表达式;
- 实现 Callable 函数式接口的 call 方法;
- 子类实现 Callable,传参给 FutureTask 的构造方法,FutureTask 是 Runnable 的实现类,传参给 Thread 的构造方法;
- 匿名内部类;
- lambda 表达式;
- 线程池创建线程(该线程不会轻易结束,可以反复用于执行任务队列的任务);
- 线程池可以通过工厂类 Executors 去构造,如 newFixedThreadPool,固定线程池线程数的生产方法;
- 提交 Runnable、Callable 实现类/匿名内部类/lambda 表达式到线程池 ExecutorService;
4. Runnable 和 Callable 有什么区别?
- Runnable 的 run 方法没有返回值;
- Callable 的 call 方法有返回值,通过 FutureTask 对象获取结果;
- Callable 接口的 call 方法允许 throws 异常,但是 Runnable 接口的 run 方法不允许
- 因为在接口声明方法的时候,Callable 的接口方法有 throws,而 Runnable 没有;
- 所以 Callable 接口方法的实现方法自然就可以 throws 也可以不 throws,Runnable 则不能 throws;
5. run 方法 和 start 方法有什么区别?
- start 方法是用来启动线程的,通过该线程调用 run 方法执行 run 方法中所定义的逻辑;
- 一个线程 start 只能执行一次;
- run 方法则只是封装了业务逻辑的代码,是要被线程执行的;
- 就是个普通的 Java 方法,当然也就可以多次调用;
6. 线程包括哪些状态?状态之间是如何变化的?
回答:
- 线程状态包括:
- 未启动的**“新建”状态**(NEW);
- 可执行的**“就绪”/“运行”状态**(RUNNABLE);
- 没有获得锁的**“阻塞”状态**(BLOCKED);
- 调用 wait 方法的**“等待”状态**(WAITING);
- 调用 sleep 方法的**“计时等待”状态**(TIMED_WAITING);
- 线程执行结束后的**“终止”状态**(TERMINATED);
补充:
对于join方法,如果有时间限制就是 TIMED_WAITING,否则是 WAITING;
回答:
- 创建线程对象时,是新建状态;
- 调用了 start 后,是可执行状态;
- 参与锁竞争,没有获得锁,是阻塞状态,获得锁则变回可执行状态;
- 调用 wait 方法,是等待状态,被其他线程调用 notify 唤醒后,变回可执行状态;
- 调用 sleep 方法,是计时等待状态,时间一到,变回可执行状态;
- 执行完成,是终止状态;
可执行状态,跟有无 CPU 执行无关,因为 CPU 调度是无序的,如果没有锁、wait、sleep、join 限制,“有无 CPU 执行”几乎是同时;
7. 新建 T1、T2、T3 三个线程,如何保证它们顺序执行?
当然,也可以用 wait 与 notify;
8. notify 与 notifyAll 的区别?
synchronized(obj) {obj.notifyAll();
}
- notifyAll:唤醒 wait 这把锁的所有线程参与锁竞争;
synchronized(obj) {obj.notify();
}
- notify:唤醒 wait 这把锁的随机一个线程参与锁竞争;
如果 synchronized 代码块还没有结束,那么 notify/notifyAll 的线程会继续参与锁竞争;
9. wait 和 sleep 的区别?
共同点:
- wait()、wait(long)、sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态;
不同点:
- 方法归属不同:
- sleep(long) 是 Thread 的静态方法;
- 而 wait 方法都是 Object 的成员方法,任何对象都能调用;
- 醒来的时机不同:
- sleep(long) 会在相应毫秒后醒来;
- wait 方法则需要被 notify 唤醒,而如果不唤醒 wait() 会一直等下去,wait(long) 会在相应毫秒后醒来;
- 但是它们都可以被打断而被唤醒;
- 锁特性不同:
- sleep 方法如果获得锁后执行,不会释放锁
- (放弃 CPU,我睡一会儿,期间你们也用不了);
- 但是 sleep 方法不需要获得锁就能执行;
- wait 方法则必须在获得锁的前提下,主动释放锁,并且没到时间/没被唤醒,不会参与锁竞争
- (我放弃 CPU,但是你们还可以用);
- sleep 方法如果获得锁后执行,不会释放锁
10. 如何停止一个正在运行的线程?
有三种方式可以停止线程:
-
退出标志法,使用一个能够被线程 run 方法获取到的布尔类型变量,当别的线程将其设置为 true 时,则线程结束;
- 当然,这里更加体现的是,程序员自定义去安装某个规则/条件去中断线程;
-
使用 stop 方法强制终止(不推荐)
-
使用 interrupt 方法中断线程:
- 打断正常的线程,可以通过 isInterrupted() 方法来判断是否被要求中断,由程序员决定做什么收尾;
- 打断阻塞(sleep、wait、join、等待锁释放)的线程,线程抛出 InterruptedException 异常,当然,你处理异常后仍然可以用 isInterrupted() 方法来判断是否被要求中断,由程序员决定做什么收尾;
不要不按套路出牌,isInterrupted() 方法为 true ,正确的做法就是收尾了,而不是置之不理,这样 interrupt 方法就没有意义了;