前言
本文的主要内容是多线程的一些基础知识以及 Java 创建线程的方式
1. 什么是进程与线程 / 进程于线程的区别
进程是程序的一次执行过程,系统运行一个程序即是一个进程从创建,运行到消亡的过程,在 Java 中启动 main 函数其实就是启动了一个进程,同时这个进程也是一个线程,称为主线程。
线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享进程的堆和方法区资源,但每个线程有自己的程序计数器、虚拟机栈和本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。
2. 并发和并行的区别
- 并发: 同一时间段,多个任务都在执行 (单位时间内不一定同时执行);
- 并行: 单位时间内,多个任务同时执行。
3. 为什么需要多线程
- 线程是轻量级的进程,是程序执行的最小单位,线程间的切换和调度的成本远远小于进程
- 现在的系统动不动就要求百万级甚至千万级的并发量,多线程并发编程是高并发系统的基础
- 多核时代,多线程可以提高 CPU 的利用率
4. 使用线程可能带来的问题
并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、死锁、线程不安全等等
5. 什么是线程死锁,如何避免线程死锁
如下图,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。
避免线程死锁,从死锁产生的四个必要条件入手:
- 破坏互斥条件 :这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
- 破坏请求与保持条件 :一次性申请所有的资源。
- 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
6. 什么是上下文切换
在多核编程中,线程数一般大于 CPU 的核心数,而一个 CPU 核心同一时间只能被一个线程使用,为了让线程都得到有效的执行,CPU 采取了时间片轮转算法,当一个线程时间片用完后会重新处于就绪状态,把 CPU 资源让给其他线程使用,这个过程就是一次上下文切换。
7. 线程的生命周期
结合上图:
- 当线程线程被创建后处于 **New(新建)**状态,当调用
start()
方法后开始运行,进入了 **Ready(就绪)**状态。就绪状态的线程如果获得了 CPU 的时间片,就会处于 **Running(运行)**状态。Java 将就绪态和运行态统称为 **Runnable(运行)**状态。 - 当线程执行
wait()
方法后,线程进入 Waiting(等待)状态,这个状态需要依靠其他线程通知才能返回到运行状态。 - **TimeWaiting(超时等待)**则是等待的基础上加了时间限制,比如调用了
sleep(long mills)
,wait(long mills)
,当时间到达后会自动返回运行状态。 - 当线程调用同步方法时,没有获取到锁,便会进入 **Blocked(阻塞)**状态。
- 线程执行了
run()
方法后就会进入终止状态。
8. sleep() 方法和 wait() 的区别
sleep()
方法没有释放锁,而wait()
方法释放了锁- 两者都可以暂停线程的执行
wait()
通常被用于线程间交互/通信,sleep()
通常被用于暂停执行wait()
方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的notify()
或者notifyAll()
方法。sleep()
方法执行完成后,线程会自动苏醒。或者可以使用wait(long timeout)
超时后线程会自动苏醒
9. 在 Java 中如和创建线程
- 实现
Callable
接口:将线程的任务从线程的子类中分离了出来,进行了单独的封装 - 继承
Thread
类(不推荐):Java 是有单继承的局限性,继承了 Thread 类就无法继承其他类
实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。
1)实现 Runnable 接口
需要实现 run()
方法,通过 Thread
调用 start()
方法来启动线程
创建:
public class MyRunnable implements Runnable {
public void run() {
// do something
}
}
运行:
public static void main(String[] args) {
MyRunnable instance = new MyRunnable();
Thread thread = new Thread(instance);
thread.start();
}
2)实现 Callable 接口
与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。
创建:
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}
运行:
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
3)继承 Thread 类
创建:
public class MyThread extends Thread {
public void run() {
// do something
}
}
运行:
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}