进程

  • 进程(process) 是计算机中程序的一次运行活动,是系统进行资源分配和调度的基本单位(是程序的一次执行过程,是系统运行程序的基本单位),是操作系统结构的基础。进程可以视为程序的一个实例。大部分程序可以同时运行多个实例进程(例如记事本、画图、浏览器等),也有的程序只能启动一个实例进程(例如网易云音乐、360 安全卫士等)

线程

  • 线程是进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。(拥有自己的栈),线程好比做工厂里面的流水线,如果一个工厂里面只有一条流水线,那么这个工厂的效率就必然会非常的底下,如果由多条流水线那么这个工厂的效率就会非常的高,这就是多线程的意义所在

image

线程和进程的关系:

  • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。
  • 系统将资源分配给进程,同一进程的所有线程共享该进程的所有资源。
  • 线程在执行过程中,需要协作同步(生产者消费者)。不同进程的线程间要利用消息通信的办法实现同步。
  • CPU是分给线程,即真正在CPU上运行的是线程。

创建线程

package Chapter08;

public class Process_01 {
public static void main(String[] args) {

// TODO 线程
// Thread是线程类。
// currentThread 方法用于获取当前正在运行得线程
// getName 方法用于获取线程得名称


// TODO 创建线程
//Thread t = new Thread();
MyThread t = new MyThread();

// TODO 构建线程对象时,可以只把逻辑传递给这个对象
// 传递逻辑时,需要遵循规则:() -> { 逻辑 }
// Thread t6 = new Thread(() -> {
// System.out.println("线程执行1");
// });
// t6.start();

// TODO 启动线程
t.start();
// t.stop(); // 现在已经不推荐使用,线程会在执行完毕后自动关闭

// main方法运行在main线程中
System.out.println(Thread.currentThread().getName()); // 输出为: main
}
}

// TODO 声明自定义线程类
class MyThread extends Thread {
// 重写运行指令 - (输出我自定义的线程名字)
public void run() {
System.out.println("MyThread : " + Thread.currentThread().getName());
}
}

/*
* 输出的结果为:
* main
* MyThread : Thread-0
*
* 因为主线程main不需要准备什么且一直在运行而另一个线程则需要一些准备工作,因此主线程main要先执行
* */

线程的生命周期

  • Java 语言中线程有六种状态,分别是:
    1. NEW 初始状态
      • 创建一个线程对象后,该线程对象就处于新建状态,此时它不能运行,与其他Java对象一样,仅仅由Java虚拟机为其分配了内存,没有表现出任何线程的动态特征。
    2. RUNNABLE 可运行状态/运行状态
      • 当线程对象调用了start()方法后,该线程就进入就绪状态。处于就绪状态的线程位于线程队列中,此时它只是具备了运行的条件,能否获得CPU的使用权并开始运行,还需要等待系统的调度。
      • 如果处于就绪状态的线程获得了CPU的使用权,并开始执行run()方法中的线程执行体,则该线程处于运行状态。一个线程启动后,它可能不会一直处于运行状态,当运行状态的线程使用完系统分配的时间后,系统就会剥夺该线程占用的CPU资源,让其他线程获得执行的机会。需要注意的是,只有处于就绪状态的线程才可能转换到运行状态。
    3. BLOCKED 阻塞状态
      • 一个正在执行的线程在某些特殊情况下,如被人为挂起或执行耗时的输入/输出操作时,会让出CPU的使用权并暂时中止自己的执行,进人阻塞状态。线程进人阻塞状态后,就不能进入排队队列。只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
    4. WAITING 无时限等待
      • 休眠态:一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
    5. TIMED_WAITING 有时限等待
      • 指定时间休眠态:基本同WAITING状态,多了个超时参数,调用对应方法时线程将进入TIMED_WAITING状态,这一状态将一直保持到超时期满或者接收到唤醒通知,带有超时参数的常用方法有Thread.sleep、锁对象.wait()
    6. TERMINATED 终止状态
      • 如果线程调用stop()方法或run()方法正常执行完毕,或者线程抛出一个未捕获的异常(Exception)错误(Error),线程就进入死亡状态。一旦进入死亡状态,线程将不再拥有运行的资格,也不能再转换到其他状态。

image

image

线程的执行方式:同步异步(Synchronize, Asynchronize)

  • 可以理解为js中的同步与异步操作
package Chapter08;

public class Process_02 {
public static void main(String[] args) throws Exception {

// TODO 线程 - 执行方式(串行,并发)
// 串行执行:多个线程连接成串,然后按照顺序执行
// 并发执行:多个线程是独立,谁抢到了CPU得执行权,谁就能执行

MyThread1 t1 = new MyThread1();
MyThread2 t2 = new MyThread2();

// 开启线程
t1.start();
t2.start();

// 将线程连接成串
t1.join();
t2.join();

System.out.println("main线程执行完毕");
}
}

// TODO 第一个线程
class MyThread1 extends Thread {
// 重写运行指令
public void run() {
System.out.println("MyThread-1 : " + Thread.currentThread().getName());
}
}
// TODO 第二个线程
class MyThread2 extends Thread {
// 重写运行指令
public void run() {
System.out.println("MyThread-2 : " + Thread.currentThread().getName());
}
}

/*
* 程序运行结果为: 先执行线程1,再执行线程2,最后在执行主线程
* MyThread-2 : Thread-1
* MyThread-1 : Thread-0
* main线程执行完毕
*
* 线程是默认是并行执行的,可以理解为js中的异步操作,谁先执行主要是看哪一个线程先准备好,这就存在了非常大的
* 不确定性,使用.join可以使得线程按照我们想要的顺序来执行
* */

使用线程池创建多线程

  • 使用线程池创建多线程的方式,也是我们开发中最常用的方式,在多线程编程中,频繁地创建和销毁线程是一种比较消耗资源的操作。而线程池可以在程序启动时就创建一定数量的线程,并重复使用它们来处理任务。这样可以避免线程频繁创建和销毁的开销,提高了程序的执行效率。同时,线程池还可以控制线程的数量,避免线程过多导致资源耗尽或线程过少导致任务处理速度过慢。因此,使用线程池可以更好地平衡系统的负载,提高程序的性能和响应速度。

使用线程池的好处

  1. 背景:经常创建和销毁、使用最特别大的资源,比如并发情况下的线程,对性能影响很大。
  2. 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毀、实现重复利用。类似生活中的公共交通工具。

线程池的各个参数详细介绍

  1. corePoolSize(核心线程数):
    • corePoolSize表示线程池中的核心线程数量,即线程池中始终保持的活动线程数量。如果当前线程池中的线程数量小于核心线程数,会直接创建新的线程来执行任务。
  2. maximumPoolSize(最大线程数):
    • maximumPoolSize表示线程池中允许的最大线程数量。如果当前线程池中的线程数量大于等于核心线程数,但任务队列未满,任务会被放入队列中等待执行。如果任务队列已满,但当前线程池中的线程数量小于最大线程数,会创建新的线程来执行任务。
  3. keepAliveTime(线程空闲时间) :
    • keepAliveTime表示线程空闲时的存活时间。如果线程空闲时间超过keepAliveTime,且线程池中的线程数量大于核心线程数,多余的线程会被销毁,以减少资源消耗。
  4. unit(时间单位):
    • unitkeepAliveTime的时间单位,可以是秒、毫秒、微秒等。通过合理设置keepAliveTimeunit,可以控制线程空闲时间的精度。
  5. workQueue(任务队列):
    • workQueue是用于存放待执行任务的队列。线程池中的线程会从任务队列中获取任务并执行。常用的任务队列有ArrayBlockingQueueLinkedBlockingQueueSynchronousQueue等。
  6. threadFactory(线程工厂):
    • threadFactory用于创建新的线程对象。通过实现ThreadFactory接口,可以自定义线程的创建方式。例如,可以设置线程的名称、优先级等。
// 使用线程池创建多线程
package Chapter08;

import java.util.concurrent.*;

public class Process_04 {
public static void main(String[] args) throws Exception {
// TODO 线程 - 线程池
// 所谓得线程池,其实就是线程对象得容器
// 可以根据需要,在启动时,创建一个或者多个线程对象
// Java种有4种比较常见得线程池
// 1. 创建固定数量得线程对象
// ExecutorService是线程服务对象(结果返回生成的是随机数)
//ExecutorService executorService = Executors.newFixedThreadPool(3);
// 2. 根据需求动态创建线程(结果返回生成的是由0-10)
ExecutorService executorService = Executors.newCachedThreadPool();
// 3. 单一线程(结果返回生成的全是1)
//ExecutorService executorService = Executors.newSingleThreadExecutor();
// 4. 定时调度线程
// ExecutorService executorService = Executors.newScheduledThreadPool(3);

// 提交线程调度
// 2.执行指定的线程的操作,需要提供Runnable接口或者Callable接口实现类的对象
executorService.submit(new MyRun());
executorService.submit(new MyCall());

// 3.关闭连接池
executorService.shutdown();
}
}

// 创建多线程接口
// 实现Runnable接口
class MyRun implements Runnable{
@Override
public void run() {
// 循环遍历创建10个线程任务
for (int i=1;i<=10;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}

// 实现Callable接口
class MyCall implements Callable {
@Override
public Object call() throws Exception {
int sum = 0;
// 循环遍历创建10个线程任务
for (int i=1;i<=10;i++){
sum += i;
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return sum;
}
}

image