前言
并发编程的目的是为了让程序运行得更快,但是,并不是启动更多的线程就能让程序最 大限度地并发执行。在进行并发编程时,如果希望通过多线程执行任务让程序运行得更快,会 面临非常多的挑战,比如上下文切换的问题、死锁的问题,以及受限于硬件和软件的资源限制问题。这一块内容也是面试核心考点之一,所以博主将以线程为起点,从0到1一起与小伙伴们走去Java并发编程之路上走一遭!
正文
进程?线程?傻傻分不清?
何谓进程
进程通常是程序、应用的同义词。进程的本质是一个正在执行的程序,程序运行时系统会创建一个进程,并且给每个进程分配独立的内存地址空间保证每个进程地址不会相互干扰。同时,在 CPU 对进程做时间片的切换时,保证进程切换过程中仍然要从进程切换之前运行的位置出开始执行。所以进程通常还会包括程序计数器、堆栈指针。下面来一张图,大致了解一下并发进程张什么样子:
有了进程以后,可以让操作系统从宏观层面实现多应用并发。而并发的实现是通过 CPU 时间片不端切换执行的。对于单核 CPU 来说,在任意一个时刻只会有一个进程在被CPU 调度。
既生进程,何生线程
- 在多核 CPU 中,利用多线程可以实现真正意义上的并行执行。
- 在一个应用进程中,会存在多个同时执行的任务,如果其中一个任务被阻塞,将会引起不依赖该任务的任务也被阻塞。通过对不同任务创建不同的线程去处理,可以提升程序处理的实时性。
- 线程可以认为是轻量级的进程,所以线程的创建、销毁比进程更快。
何谓线程
线程(Thread)的英文原意是“细丝”,Java语音上把“正在执行程序的主体”称为线程(Thread)。在一个软件内,我们也能同时干多个事,这些不同的功能可以同时进行,是因为在进程内使用了多个线程。线程有时又称之为轻量级进程。但是创建一个线程要消耗的资源通常比创建进程少的多。
注:不是只有Java程序处理系统上执行的才叫线程,垃圾收集器回收垃圾执行的也叫线程,还有GUI相关线程等等。
线程与进程的关系
一个进程内的多个线程会共享进程的资源,同时也会有自己私有的资源。线程必须存在于进程中,每个进程至少要有一个线程作为程序的入口。线程是可以并发执行的,所以我们在一个软件内也可以同时干多个事。操作系统上通常会同时运行多个进程,每个进程又会开启多个线程。一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。
Java中线程的分类
Java线程主要分为两类:用户线程(非守护线程)和守护线程。用户线程是应用程序运行时执行在前台的线程,守护线程是应用程序运行执行在后台的线程,也就是程序运行的时候在后台为非守护线程提供一种通用服务的线程。比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
守护线程和用户线程的没啥本质的区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
单线程程序
想一想我们读代码的一个过程,接下来这句话,你细品。明为追踪处理流程,实则实在追踪线程。接下来我以图解形式描述一下:
解读代码的过程就是一个追踪流程的过程,也是一个追踪线程的过程。我们将代码比作一条没有分支大河,无论是方法调用、for循环、if判断还是更为复杂的处理都无所谓,只要这个程序的处理流程从头到尾就只有一条线,那么这个程序就是单线程程序。在单线程程序中,“正在执行程序的主体”永远只有一个。
线程的生命周期
线程是一个动态执行的过程,它也有一个从产生到死亡的过程。下面以流程图方式展现一个线程完整的生命周期:
- 新建状态
使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。 - 就绪状态
当线程对象调用了start()方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。 - 运行状态
如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。 - 阻塞状态
如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。 - 死亡状态
一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
线程的优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
public final void setPriority(int newPriority) : 更改此线程的优先级。
static int MAX_PRIORITY :线程可以拥有的最大优先级。 (值 为10)
static int MIN_PRIORITY :线程可以拥有的最小优先级。 (值 为1)
static int NORM_PRIORITY :被分配给线程的默认优先级。 (值为5)
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级并不能保证线程执行的顺序,也就是说优先级高的线程不一定就会先执行,因为它有很大的随机性,只有抢到CPU资源的线程才会执行,优先级高的线程只是抢占到CPU资源的机会要更大一点,线程的执行顺序真正取决于CPU调度器(在Java中是JVM来调度),我们是无法控制。
这里也为小伙伴们准备了一段代码,你们可以多运行几次看看效果体会一下优先级:
public class PriorityDemo {
public static void main(String[] args) {
//创建线程max最大
Thread max=new Thread() {
public void run() {
for(int i=0;i<100;i++) {
System.out.println("max");
}
}
};
//创建线程min最小
Thread min = new Thread() {
public void run() {
for(int i=0;i<100;i++) {
System.out.println("min");
}
}
};
//创建线程norm默认
Thread norm = new Thread() {
public void run() {
for(int i=0;i<100;i++) {
System.out.println("norm");
}
}
};
max.setPriority(Thread.MAX_PRIORITY);//将线程max设置为最大值10
min.setPriority(Thread.MIN_PRIORITY);//将线程min设置为最小大值1
min.start();
norm.start();
max.start();
}
}
如何创建线程
1.通过实现 Runnable 接口来创建线程
创建一个线程,最简单的方法是创建一个实现 Runnable 接口的类。为了实现 Runnable,一个类只需要执行一个方法调用 run(),线程创建之后调用它的 start() 方法它才会执行。
Thread 定义了几个构造方法,下面的这个是我们经常使用的:
//threadOb 是一个实现 Runnable 接口的类的实例
//threadName是线程的名字
Thread(Runnable threadOb,String threadName);
代码示例:
public class RunnableDemo implements Runnable{
private Thread thread;
private String threadName;
RunnableDemo( String name) {
threadName = name;
System.out.println("Creating " + threadName );
}
public void run() {
System.out.println("Running " + threadName );
try {
for(int i = 1; i <= 2; i++) {
System.out.println("Thread: " + threadName + ",第" + i + "次执行");
// 让线程睡眠一会
Thread.sleep(50);
}
}catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start () {
System.out.println("Starting " + threadName );
if (thread == null) {
thread = new Thread (this, threadName);
thread.start ();
}
}
public static void main(