进程是程序执行的一次过程,是系统运行程序的基本单位。进程的本质是一个正在执行的程序。在 CPU 对进程做时间片的切换时,保证进程切换过程中仍然要从进程切换之前运行的位置出开始执行。所以进程通常还会包括程序计数器、堆栈指针。
线程是正在执行程序的主体,线程有自己的程序计数器、虚拟机栈以及本地方法栈。创建线程消耗的资源一般要比创建进程小号的资源小得多,因此线程也被称为轻量级进程。
线程是进程划分成更小的程序执行单位,一个进程中可以有多个线程,多个线程共享进程的堆和方法区资源。多个进程之间是相互独立的,但是同一个进程中的多个线程之间可能会相互影响。一个线程是不能独立存在的,它必须是进程的一部分。
并发:同一个时间段内,多个任务都在执行(CPU不断进行上下文的切换,从用户角度来看多个任务是一起执行的,实际上在同一个时间点只有一个任务在执行)。
并行:同一个时间点,多个任务同时执行。
一个任务在执行完CPU时间片准备切换到另一个任务之前会先保存自己执行的位置,以便下次再切换到这个任务的时候可以接着上次结束的位置执行。任务从保存到再次加载的一个过程就叫上下文切换。
Java线程在运行的生命周期中一共有以下六种状态:
内存泄漏、频繁上下文切换、死锁以及受限于硬件和软件资源等。
多个线程同时被阻塞,它们都在等待其他线程释放资源而全部被无限制的阻塞。举个例子,线程A持有资源1,线程B持有资源2,线程A只有拿到资源2才会执行结束,线程B只有拿到资源1才会执行结束,双方都想拿到对方的资源,所以会无限制的互相等待从而进入死锁状态。
死锁必须具备的四个条件:
上面讲过死锁的四个必备条件,那么只要破坏其中一个条件,就可以避免死锁。
为了方便理解,下面写一个死锁的小例子:
public class DeadLockDemo {
private static Object resource1 = new Object();//资源 1
private static Object resource2 = new Object();//资源 2
public static void main(String[] args) {
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource2");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
}
}
}, "线程 1").start();
new Thread(() -> {
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource1");
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
}, "线程 2").start();
}
}
new Thread(() -> {
synchronized (resource1) {
System.out.println(Thread.currentThread() + "get resource2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread() + "waiting get resource1");
synchronized (resource2) {
System.out.println(Thread.currentThread() + "get resource1");
}
}
}, "线程 2").start();
如果你直接调用run()方法,JVM是不会去创建线程的,run()方法只能用于已有线程中。Java里面创建线程之后必须要调用start()方法才能真正的创建一个线程并且让线程处于就绪状态,该方法会调用虚拟机启动一个本地线程,本地线程的创建会调用当前系统创建线程的方法进行创建,并且线程被执行的时候会回调 run()方法进行业务逻辑的处理。
join作用是让其他线程变为等待,只有当前线程执行完毕后,等待的线程才会被释放。
当其他线程通过调用当前线程的 interrupt ()方法,表示向当前线程打个招呼,告诉他可以中断线程的执行了,至于什么时候中断,取决于当前线程自己。线程通过检查自身是否被中断来进行相应,可以通过
isInterrupted()来检查自身是否被中断。
stop 方法在结束一个线程时并不会保证线程的资源正常释放,因此会导致程序可能出现一些不确定的状态,相当于我们在linux上通过kill -9强制结束一个进程。
线程中提供了静态方Thread.interrupted()对设置中断标识的线程复位,还有一种被动复位的场景,就是抛出了一个InterruptedException 异常,在InterruptedException 抛出之前,JVM 会先把线程的中断标识位清除,然后才会抛出 InterruptedException,这个时候如果调用 isInterrupted 方法,将会返回 false。
Thread.interrupted()是属于当前线程的,是当前线程对外界中断信号的一个响应,表示自己已经得到了中断信号,但不会立刻中断自己,具体什么时候中断由自己决定,让外界知道在自身中断前,他的中断状态仍然是 false,这就是复位的原因。
如果你想了解更多的关于线程启动、中断、复位相关的底层原理,想知道在虚拟机层面它到底做了什么,可以点击这里Java并发编程(一)之线程