基本概念
进程
进程是操作系统资源分配的基本单位,操作系统在运行时会为每个进程分配不同的内存区域。进程是程序的一次执行过程:程序运行,加载到内存中,占用了cpu的资源。
线程
线程是cpu调度和执行的基本单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销比进程小。
同一个进程中的多个线程共享相同的内存单元,线程从同一堆中分配对象,可以访问相同的变量和对象。每个进程都会有一个方法区和堆,多个线程共享同一进程下的方法区和堆。多个线程操作同一个共享数据,这就可能导致线程安全问题。
多线程的优点:提高应用程序的响应效率,可以增强用户体验。提高计算机CPU的利用率。
需要多线程的时机:
- 需要同时执行多个任务。
- 需要在后台运行的程序。
- 实现一些需要等待的任务时,文件读写操作、网络操作等。
创建线程和启动线程
java多线程实现
多线程可以通过Java中的Thread类来体现,线程的主题逻辑是通过Thread对象的run()方法来操作的,把run()方法的主体称为线程体。通过Thread的start()方法来启动这个线程。
创建多线程
继承Thread类
- 创建继承于Thread类的子类。
- 重写Thread类的run()方法。
- 建这个子类的实例对象。
- 通过此对象调用start()来启动线程。
代码:
public class Thread1 extends Thread {
// 启动线程时会运行run方法中的代码
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 3 == 0){
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
public static void main(String[] args) {
Thread1 t1 = new Thread1();
t1.start();
Thread1 t2 = new Thread1();
t2.start();
System.out.println("main方法");
}
}
运行结果:
main方法
Thread-0: 0
Thread-1: 0
Thread-1: 3
Thread-0: 3
Thread-0: 6
Thread-1: 6
Thread-0: 9
Thread-1: 9
实现Runnable接口
- 创建一个实现Runnable接口的类class1。
- 在实现类重写run方法。
- 创建类class1的实例对象obj1。
- 创建Thread类的对象,将此对象obj1作为构造函数参数。
- 调用Thread类的实例对象的start方法。
代码:
public class Thread1 {
public static void main(String[] args) {
RunnableThread1 runnableThread01 = new RunnableThread1();
Thread t1 = new Thread(runnableThread01);
t1.start();
Thread t2 = new Thread(runnableThread01);
t2.start();
System.out.println("main方法");
}
}
class RunnableThread1 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 3 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
实现Callable接口
代码省略
三种实现多线程方式的比较
- 用实现Runnable接口的方式来实现多线程更加实用。
- 继承Thread实现多线程的方式,需要创建多个子类的对象来进行多线程,而实现Runnable接口的实现多线程方式,只需要创建一个实现类的对象,要将这个对象传入Thread类并创建多个Thread类的对象来完成多线程。
- 实现Runnable接口的方式更适合处理多个线程有共享数据的情况。
- 实现Runnable接口和继承Thread两种方式都需要重写run方法,线程的核心执行逻辑都在run方法中。
- 用实现Callable接口的方式功能更强大,相比run方法,可以有返回值,方法可以抛出异常,支持泛型的返回值。
用线程池实现多线程
使用线程池实现多线程减少了创建新线程的时间,可以提高响应速度;可以重复利用线程池中线程,降低资源消耗;便于管理线程。
代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
class ThreadClass implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
if (i % 3 == 0) {
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}
}
}
public class Thread1 {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new ThreadClass()); //用于实现Runnable的方式
service.execute(new ThreadClass());
//service.submit(); 适用于实现Callable的方式
//关闭线程池
service.shutdown();
System.out.println("main方法");
}
}
结果:
pool-1-thread-2: 0
main方法
pool-1-thread-1: 0
pool-1-thread-2: 3
pool-1-thread-1: 3
pool-1-thread-1: 6
pool-1-thread-2: 6
pool-1-thread-1: 9
pool-1-thread-2: 9
Thread常用方法
- start() : 启动线程
- run() : 线程核心执行逻辑
- yield() : 释放CPU
- join() : 阻塞作用,如果在线程a中调用线程b的join(), 此时线程a进入阻塞状态, 直到线程b完全执行完以后, 线程a才结束阻塞状态
- sleep(long militime) : 让线程睡眠指定的毫秒数,在指定时间内,线程是阻塞状态
- currentThread() : 静态方法, 返回当前代码执行的线程
- getName() : 获取当前线程的名字
- setName() : 设置当前线程的名字
线程的生命周期
- 新建:Thread子类的实例对象被声明并创建时,线程对象处于新建状态。
- 就绪:处于新建状态的线程start后,将进入线程队列等待CPU时间片,此时它没分配到CPU资源。
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态。
- 阻塞:被挂起或执行输入输出操作时,让出CPU并临时中止执行,进入阻塞状态。
- 死亡:线程完成了它的全部工作或线程被提前强制中止或出现异常。