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 接口:
- 实现 Callable 接口,实现其中的 call() 方法,并返回执行结果
- 创 建 FutureTask 对象,将 Callable 实现类的对象传入
- 创建线程对象,将 FutureTask 对象传入
- 通过 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 服务器
- 响应事件:可以创建多个线程来响应用户的事件,例如鼠标点击事件
- 数据处理:可以创建多个线程来处理大量数据,例如在进行数据分析时