JUC基础知识

0 阅读4分钟

进程和线程的区别

进程

  • 定义:进程是程序在执行过程中的一个实例,它是操作系统分配资源(如内存、文件句柄等)和调度的基本单位。
  • 结构:每个进程都有自己的地址空间,代码段、数据段和堆栈。在进程之间,地址空间是相互独立的,进程间的通信通常需要通过特定的机制(如管道、消息队列、共享内存等)。
  • 状态:进程可以处于就绪、运行、阻塞等状态。操作系统负责调度这些进程,决定哪一个进程可以使用CPU。
  • 实例:多数应用程序(如文本编辑器、网页浏览器等)可以同时有多个进程实例,而某些应用(如系统服务和某些限制性程序)则通常只有一个实例。

线程

  • 定义:线程是进程中的一个执行单元,它是程序的最小执行单位。一个进程可以创建多个线程,这些线程共享进程的资源。
  • 结构:每个线程拥有自己的堆栈和寄存器,但它们共享进程的地址空间,包括代码段、数据段和打开的文件等。
  • 并发:因线程共享资源,因此在多线程程序中需要进行适当的同步,避免数据竞争和不一致的问题。常用的同步机制包括互斥锁(mutex)、信号量(semaphore)等。
  • 调度:在线程级别,调度较快,因为线程之间的切换相对轻便(比进程切换开销小)。在Java中,线程是最小的调度单元,线程由操作系统调度运行。

并发和并行

单核CPU下,线程实际是串行执行的。操作系统的任务调度器,把CPU的时间片( windows下时间片最小约为15毫秒 )分给不同的程序使用,只是CPU在线程间的切换非常快,让人感觉好像是同时运行的。总结为:微观串行,宏观并行。

  • 并行 (Parallelism):同一时刻有多个指令在多个处理器(CPU)或核心上同时执行。
  • 并发 (Concurrency):同一时刻有多个指令在单个处理器(CPU)上交替执行。

Java线程

创建线程

Thread

  • 线程启动需要调用 start()方法,如果线程直接调用 run() 方法,那么由主线程进行执行
  • 当线程A调用 start() 方法时,Java 虚拟机会创建一个新的线程B并调用线程B的 run() 方法,这意味着,调用 start() 方法后,当前线程A和新线程B会同时运行。当前线程A是执行 start() 调用的线程,而新线程B则执行其 run() 方法

Thread 构造器:

  1. public Thread()
  2. public Thread(String name)
public class ThreadDemo {
  // 创建线程对象
  Thread t1 = new Thread("t1") {
  @Override
  // run 方法内实现了要执行的任务
  public void run() {
  log.debug("hello");
      }
   };
  t1.start();
}
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i = 0 ; i < 10 ; i++ ){
            System.out.println(Thread.currentThread().getName() + "->" + i);
        }
    }
}

Runnable

  1. public Thread(Runnable target)
  2. public Thread(Runnable target, String name)

Thread代表线程,Runnable表示可运行的任务

Runnable runnable = new Runnable() {
 public void run(){
 // 要执行的任务
    }
 };
 // 创建线程对象
Thread t = new Thread( runnable );
 // 启动线程
t.start(); 

Thread 类本身也是实现了 Runnable 接口,Thread 类中持有 Runnable 的属性,执行线程 run 方法底层是调用 Runnable的run():

使用 Runnable 的优点:

  1. 线程任务类只是实现了 Runnable 接口,可以继续继承其他类,避免了单继承的局限性
  2. 同一个线程任务对象可以被包装成多个线程对象
  3. 适合多个多个线程去共享同一个资源
  4. 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立
  5. 线程池可以放入实现 Runnable 或 Callable 线程任务对象

Callable

Callable是用于定义可返回结果的任务的接口,与Runnable接口相比,核心在于可返回结果和允许抛出异常


FutureTask 是 Runnable 和 Future 接口的实现类,用于包装 Callable 或 Runnable 任务,使其具备以下能力:

  • 异步执行:作为 Runnable 被线程执行
  • 结果管理:通过 Future 接口获取任务结果、取消任务、查询任务状态
  1. get() 阻塞获取任务结果,直到任务完成或抛出异常

(run() 执行完后会把结果设置到 FutureTask 的一个成员变量,get() 线程可以获取到该变量的值)

  1. get(long timeout, TimeUnit) 带超时的阻塞获取结果
// 1. 创建Callable任务
Callable<Integer> task = () -> {
    Thread.sleep(1000);
    return 42;
};

// 2. 包装为FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(task);

// 3. 启动线程执行
new Thread(futureTask).start();

// 4. 获取结果(阻塞)
int result = futureTask.get();