1. 线程和进程的区别
-
定义:
- 进程:是操作系统分配资源的基本单位,具有独立的地址空间和资源。每个进程有自己的内存和资源,进程之间相对独立。
- 线程:是进程中的一个执行单元,线程共享进程的地址空间和资源。一个进程可以包含多个线程,它们共享数据和状态。
- 区别:
- 地址空间:进程间相互独立,线程间共享地址空间。
- 资源拥有:进程拥有独立的资源,线程共享进程资源。
- 执行:进程是执行程序的独立单位,线程是CPU调度执行的单位。
- 创建销毁:进程创建和销毁开销大,线程开销小。
- 通信:进程间通信复杂,线程间通信简单。
2.举例
-
继承
Thread
类:- 创建一个子类继承
Thread
类,重写run()
方法,然后实例化该类并调用start()
方法。
- 创建一个子类继承
class MyThread extends Thread {
public void run() {
// 线程任务
}
}
MyThread thread = new MyThread();
thread.start();
2.实现Runnable
接口:
- 创建一个类实现
Runnable
接口,重写run()
方法,然后通过Thread
类实例化并传入该类的对象。
class MyRunnable implements Runnable {
public void run() {
// 线程任务
}
}
Thread thread = new Thread(new MyRunnable());
thread.start();
3. 为什么使用Runnable
代替Thread
- 解耦:使用
Runnable
接口可以将任务与线程分离,使得同一个任务可以在不同的线程中执行。 - 支持多个继承:Java不支持多重继承,如果一个类已经继承了其他类,就不能再继承
Thread
,而可以实现Runnable
。 - 资源共享:通过
Runnable
可以更方便地共享资源,因为多个线程可以共享同一个Runnable
实例。 - 代码复用:
Runnable
可以在多个不同的线程中重复使用,增加了灵活性。
4.线程的生命周期
-
新建(New):
- 线程对象被创建,但还没有调用
start()
方法。 - 这个阶段的线程还没有开始执行。
- 线程对象被创建,但还没有调用
-
就绪(可运行(Runnable)):
- 调用了
start()
方法后,线程进入可运行状态。 - 可运行状态包括了操作系统线程的就绪(Ready)和运行(Running)两个状态。
- 线程调度器(Scheduler)负责将可运行状态的线程分配到处理器上执行。
- 线程在可运行状态时可能正在运行,也可能正在等待CPU时间片。
- 调用了
-
阻塞(Blocked):
- 线程在尝试获取一个锁(synchronized关键字同步的代码块或方法)时,如果该锁正被其他线程占用,那么当前线程将进入阻塞状态。
- 线程在等待I/O操作(如读取文件或网络数据)时也可能进入阻塞状态。
- 线程在阻塞状态时不会消耗CPU资源。
-
死亡(Terminated):
- 线程的
run()
方法执行完毕后,线程进入终止状态。 - 终止状态的线程不可再次启动,其资源被回收。
- 线程的
5. 什么是守护线程,什么是用户线程
-
用户线程:
- 用户线程是普通的线程,代表程序的主要任务。用户线程的生命周期与程序的运行相关,只要用户线程在运行,Java虚拟机(JVM)就会继续运行。
-
守护线程:
- 守护线程是为其他线程提供服务的线程,例如垃圾回收线程。守护线程的生命周期依赖于用户线程,当所有用户线程结束时,JVM会自动结束守护线程。可以通过调用
setDaemon(true)
方法将线程设置为守护线程。
- 守护线程是为其他线程提供服务的线程,例如垃圾回收线程。守护线程的生命周期依赖于用户线程,当所有用户线程结束时,JVM会自动结束守护线程。可以通过调用
6.线程安全的三大特性
-
原子性(Atomic):
- 原子性是指一个操作或者一组操作要么全部执行,要么全部不执行,不存在中间状态。
- 在多线程环境中,原子性确保了操作的不可分割性,从而避免了多个线程对同一数据的并发访问导致的数据不一致问题。
- Java中的
synchronized
关键字和java.util.concurrent.atomic
包提供的原子类(如AtomicInteger
、AtomicLong
等)可以用来保证操作的原子性。
-
可见性(Visibility):
- 可见性是指当一个线程修改了共享变量的值,其他线程能够立即看到这个变化。
- 由于Java内存模型(JMM)的缓存一致性问题,一个线程对共享变量的修改可能不会立即被其他线程感知。
- 可以通过
volatile
关键字来保证变量的可见性,volatile
保证了变量的读写操作都会直接作用于主内存,从而使得其他线程能够看到最新的值。
-
有序性(Ordering):
- 有序性是指程序执行的顺序按照代码的顺序进行,不会出现指令重排导致的执行顺序混乱。
- Java编译器和处理器为了优化性能,可能会对指令进行重排,这可能会导致多线程环境下的程序出现不确定的行为。
- 可以通过
synchronized
关键字、volatile
关键字以及java.util.concurrent
包中的锁机制(如ReentrantLock
)来保证有序性。