跳到主要内容

Java 线程

什么是进程

要了解什么是线程(Thread),首先必须了解进程(Process)的基础概念。

线程是进程的一部分,一个进程至少包含一个线程。

进程是操作系统资源分配和调度的基本单位,它是一个正在执行的应用程序,由内存和外部资源组成,它是一个正在执行的应用程序。进程在电脑执行任务的时候具有一定的独立性,进程之间的相互作用可以通过系统调用实现。每个进程都有自己的地址空间,进程可以同时执行多个任务,并且可以访问共享的资源。

什么是线程

线程是一种轻量级的进程,它是操作系统在单独的地址空间中管理的一个执行单元。它是操作系统中最小的调度单元,它可以被系统独立调度,即使在同一个进程中也可以被多个线程同时调度。线程可以分配到多个 CPU 上运行,从而实现多任务同时处理,实现多线程并发处理。

图书馆示例:

  • 以一个简单的图书馆为例,通常图书馆里面都会有许多不同角色的工作人员在同步工作中,有的人负责发书,有的人负责检查书籍是否被正确归还,有的人负责图书馆大门的进出治安,可以看到,这些不同角色的人一起工作,互相协调,从而完整对整个图书馆的维护和管理
  • 我们可以将上述的 这个图书馆想象成一个进程,在这个环境里面可以同时有多个线程在运行,比如:一个线程负责收取借书人的借书卡,另一个线程负责发书人的还书卡,还有一个线程负责检查书籍是否被正确归还等等。每个线程都有自己的任务,它们可以同时运行。这种多个线程同时运作的特点,业界被称为叫“多并发”

Java 如何创建线程

在 Java 体系中,对于线程的创建主要有以下几种方式:

  • 通过实现 Runnable 接口来创建线程
  • 通过继承 Thread 类来创建线程
  • 通过 Callable 和 Future 接口来创建线程
  • 通过使用线程池来创建线程

Java 的线程对象

Thread 类是 Java 语言中实现多线程的基础,它提供了管理和控制线程的方法和接口。可以将一个 Thread 对象定义为一个单独的线程,可以直接使用 Thread 类来创建和控制线程,也可以从 Thread 类派生出自定义的线程类,以便更好地满足应用的需求。

创建 Thread 对象方式

  • new Thread() 创建一个线程对象
  • new Thread(String name) 创建一个指定名称的线程对象
  • new Thread(Runnable runnable) 创建一个线程,但是内部要有具体的 Runnable 接口实现类
  • new Thread(Runnable runnable, String name) 创建一个线程,需要有具体的 Runnable 接口实现类,以及线程的名称

示例:

public class MyThread extends Thread {
public void run(){
System.out.println("MyThread running");
}
}

public class Test {
public static void main(String[] args) {
MyThread mythread = new MyThread();
/**
* Thread 的 start 方法是启动一个线程的方法,
* 它调用了 native start0() 方法,它会调用线程的 run() 方法,
* 而 run() 方法是由用户实现的,它定义了该线程要做的事情。
* start() 方法不能被重复调用,否则会抛出 IllegalThreadStateException 异常。
*/
mythread.start(); // MyThread running
}
}

Thread 类中常用的方法

常用的方法有:

  • start() 启动线程,调用该方法后,线程就会处于就绪状态,等待被调度执行
  • run() 该方法定义了线程的具体逻辑,当多线程执行时,JVM 会选择一个线程去执行 run() 方法,其他的线程则被阻塞
  • sleep() 让当前线程暂停一段时间,单位是毫秒,但是该方法不会释放锁,只会让出CPU资源,让其他线程可以获取 CPU 时间片
  • join() 该方法可以让一个线程等待另外一个线程执行完毕后,再继续执行
  • interrupt() 中断一个线程,让线程停止运行,此时可以通过 isInterrupted() 方法查询线程是否被中断
  • isAlive() 该方法判断线程是否处于活动状态,即是否处于运行状态
  • yield() 该方法可以使当前线程让出 CPU 资源,让其他线程有机会占用 CPU 时间片,但是该方法不会释放锁
  • setPriority() 设置线程的优先级,优先级越高的线程获取 CPU 时间片的机会越多

示例:

/**
* 这段代码会启动一个新的线程,让当前线程暂停一段时间,
* 等待另外一个线程执行完毕后再继续执行,
* 中断一个线程,判断线程是否处于活动状态,
* 让当前线程让出CPU资源,并设置线程的优先级。
*/
public class TestThread {
public static void main(String[] args) {
//启动线程
Thread thread = new Thread(() -> System.out.println("Thread is running!"));
thread.start();

//让当前线程暂停一段时间
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}

//等待另外一个线程执行完毕后,再继续执行
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

//中断一个线程
thread.interrupt();

//判断线程是否处于活动状态
System.out.println(thread.isAlive());

//让当前线程让出CPU资源
Thread.yield();

//设置线程的优先级
thread.setPriority(Thread.MAX_PRIORITY);
}
}

Callable 接口的介绍

Callable 接口是一种可以返回结果的多线程接口,它继承了 Runnable 接口,但是有较大的不同,它可以返回执行结果和抛出受检查的异常,而 Runnable 接口无法返回结果或者抛出受检查的异常。Callable 接口可以通过 FutureTask 类来实现,从而实现线程的多返回值,可以被用于实现高并发的场景。

如何使用 Callable 接口:

  1. 实现 Callable 接口,实现其中的 call() 方法,并返回执行结果
  2. 创建 FutureTask 对象,将 Callable 实现类的对象传入
  3. 创建线程对象,将 FutureTask 对象传入
  4. 通过 FutureTask 的 get() 方法获取返回值

示例:

public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
// 进行任务处理,并返回处理结果
String result = "hello";
return result;
}
}

public class Test {
public static void main(String[] args) throws Exception {
MyCallable task = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(task);
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get();
System.out.println(result); // // Hello World
}
}

Java 线程的作用

在一些复杂的业务场景中,我们利用线程可以完成很多高效工作:

  • 多任务并行处理:可以创建多个线程来同时处理多个任务
  • 后台服务:可以创建一个后台线程来执行定期任务,例如定时备份数据库
  • 定时任务:可以创建定时线程来定期执行任务,例如在每天的指定时间定期备份数据库
  • 并发服务:可以创建多个线程来处理多个客户端请求,从而提供并发服务,例如 Web 服务器
  • 响应事件:可以创建多个线程来响应用户的事件,例如鼠标点击事件
  • 数据处理:可以创建多个线程来处理大量数据,例如在进行数据分析时