从零开始学Java之线程中有哪些核心API方法?

作者 :孙玉昌,昵称【一一哥 】,另外【壹壹哥】也是我哦

千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者

前言

在上一篇文章中,壹哥 给大家介绍了进程、线程的概念,以及创建线程的几种方式。现在我们对线程已经有了基本的了解,但是有些同学看了昨天的文章就来问壹哥 ,说线程的方法只有start()和run()吗?怎么没有看到别的线程方法?所以,今天壹哥要给大家介绍一些别的核心API方法,这些方法与我们的多线程开发都息息相关哦。

------------------------------前戏已做完,精彩即开始----------------------------

全文大约【5100】 字,不说废话,只讲可以让你学到技术、明白原理的纯干货!本文带有丰富的案例及配图视频,让你更好地理解和运用文中的技术概念,并可以给你带来具有足够启迪的思考......

配套开源项目资料

Github: github.com/SunLtd/Lear...

Gitee: gitee.com/sunyiyi/Lea...

一. 线程相关的核心API方法

为了让大家有个清晰的认知,壹哥会给大家分类绘制表格,来展示与线程相关的常用核心API方法及其用法。

1. Thread中的方法

Thread是Java中最基本的线程类,它提供了许多用于创建、管理和控制线程的方法,如下表所示:

方法名 返回类型 描述
start() void 启动线程,JVM会调用线程的run()方法
run() void 执行线程的核心代码块
sleep(long millis) static void 指定线程休眠的毫秒数
interrupt() void 中断线程
isInterrupted() boolean 判断线程是否被中断
join() void 把指定的线程加入到当前线程,将两个交替执行的线程合并为顺序执行的线程,并可以让线程陷入等待
getName() String 获取某个线程的名称
setName(String name) void 设置线程的名称
yield() static void 暂停当前线程,让出CPU执行时间
isAlive() boolean 判断线程是否存活
setPriority(int priority) void 设置线程的优先级
getPriority() int 获取线程的优先级
wait() final void 线程等待,释放锁
notify() final void 唤醒等待中的线程
notifyAll() final void 唤醒等待中的所有线程

注意:以上这些方法都是在Thread类中定义的,因此我们需要先创建出Thread类的实例才能使用它们。

2. Object中的方法

虽然Object本身不是线程类,但它是Java中所有类的基类,它也给我们提供了几个用于线程操作的方法,如下所示:

方法名称 方法描述
wait() 使当前线程等待,直到另一个线程调用该对象的notify()或notifyAll()方法,或者指定的时间到期。
notify() 唤醒正在等待该对象的一个线程。
notifyAll() 唤醒正在等待该对象的所有线程。

3. Runnable中的方法

Runnable接口是Java中另一个重要的线程接口,它用于定义线程的任务,常用方法如下:

方法名称 方法描述
run() 定义线程的任务。

4. Callable中的方法

Callable接口是Java 5中引入的一个新接口,它与Runnable接口类似,但它可以返回一个值。常用方法如下:

方法名称 方法描述
call() 定义线程的任务,并返回一个值。

5. Executor中的方法

Executor接口是Java中线程池的核心接口,它定义了一组执行任务的方法。常用方法如下:

方法名称 方法描述
execute(Runnable command) 将一个任务提交到线程池中执行。
submit(Callable task) 将一个带有返回值的任务提交到线程池中执行。

6. Executors中的方法

Executors类是Java中用于创建线程池的工厂类,它提供了许多静态方法来创建不同类型的线程池。常用方法如下:

方法名称 方法描述
newFixedThreadPool(int nThreads) 创建一个固定大小的线程池。
newCachedThreadPool() 创建一个有缓存的线程池。
newSingleThreadExecutor() 创建一个单线程的线程池。
newScheduledThreadPool(int corePoolSize) 创建一个支持定时及周期性执行任务的线程池。

接下来壹哥就给大家简单演示一下,以上方法中几个重点方法的用法。

二. 核心方法的使用

1. sleep()方法

sleep()方法是Thread类中的一个静态方法,可以使线程暂停指定的时间,该方法接受一个long类型的参数,表示线程需要暂停的毫秒数。调用sleep()方法会让当前线程进入阻塞状态,暂停执行,等到指定的时间过去后会被唤醒继续执行当前任务。示例代码如下:

java 复制代码
/**
 * @author 一一哥Sun
 */
public class Demo05 {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            System.out.println("当前线程:" + Thread.currentThread().getName() + ",i = " + i);
            Thread.sleep(1000); // 暂停一秒
        }
    }
}

该上面的案例中,主线程会输出数字0-9,每个数字之间暂停一秒,实现了每秒输出一次的效果。

2. join()方法

Thread.join()方法可以使一个线程等待另一个线程执行完毕后再继续执行,该方法会阻塞当前线程,直到调用该方法的线程执行完毕。比如我们在主线程中调用了另一个线程的join()方法,则主线程就会被阻塞,直到另一个线程执行完成,主线程才能继续执行。示例代码如下:

java 复制代码
/**
 * @author 一一哥Sun
 */
public class Demo06 {
	public static void main(String[] args) throws InterruptedException {
		// 采用Lambda表达式创建线程
		Thread thread1 = new Thread(() -> {
			for (int i = 0; i < 5; i++) {
				System.out.println("子线程1执行,i = " + i + ",当前线程:" + Thread.currentThread());
			}
		});

		Thread thread2 = new Thread(() -> {
			for (int i = 0; i < 5; i++) {
				System.out.println("子线程2执行,i = " + i + ",当前线程:" + Thread.currentThread());
			}
		});

		thread1.start();
		thread2.start();

		//主线程会等待两个子线程执行完毕后再继续执行
		thread1.join();
		thread2.join();

		System.out.println("主线程执行完毕,当前线程:" + Thread.currentThread());
	}
}

在上面的案例中,壹哥在主线程中启动了两个子线程,子线程分别输出数字0-4。在启动子线程后,主线程调用了thread1.join()和thread2.join()方法,使主线程等待两个子线程执行完毕后再继续执行。

3. yield()方法

Thread.yield()方法可以使当前线程放弃CPU资源,让其他线程优先执行。这就好比两个人在高速上开车,一个开奔奔的主动把路权让给了开库里南的,等库里南通过后自己才走,这就是让出了"CPU资源"。yield方法没有参数,调用该方法后,当前线程会进入就绪状态,等待重新获得CPU资源后又会继续执行。

但大家要注意,yield()方法只是告诉调度器当前线程愿意放弃CPU资源,以便其他线程能够运行。但该方法并不会阻塞当前线程,它只是让当前线程从运行状态转为就绪状态,让CPU重新调度安排。这就好比壹哥前面给大家举的开车的例子,奔奔愿意把路权让给库里南,但并不是说奔奔直接就把路权让给了库里南,而是奔奔告诉交警说:"我把路权让出来,您安排给别人使用吧"。此时交警看奔奔发扬了风格让出了路权,于是就重新调度安排了路权,让库里南先走。

java 复制代码
/**
 * @author 一一哥Sun
 */
public class Demo07 {
	public static void main(String[] args) throws InterruptedException {
		// 采用Lambda表达式创建线程
		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				System.out.println("子线程1执行,i = " + i);
				// 当前子线程放弃CPU资源
				Thread.yield();
			}
		}).start();

		new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				System.out.println("子线程2执行,i = " + i);
				// 当前子线程放弃CPU资源
				Thread.yield();
			}
		}).start();

		// 主线程
		for (int i = 0; i < 10; i++) {
			System.out.println("主线程执行,i = " + i);
			// 当前线程放弃CPU资源
			Thread.yield();
		}
	}
}

在这个案例中,我们有3个线程,1个主线程,2个子线程,我们分别在这3个线程中循环调用yield()方法,这就相当于3个人都分别发扬风格让出了自己的CPU资源,大家互相谦让。所以在多次执行时就可能会出现不同的结果,一会A线程在前,一会B线程在前,一会又可能是C线程在前。

4. interrupt()方法

当某个线程调用了interrupt()方法时,该线程就会收到一个中断信号,中断状态会被设置为true,所以interrupt()方法可以用于中断一个线程。当前线程可以在中断信号的提示下进行一些清理工作,然后结束线程的执行。如果调用了interrupt()方法的线程正在被阻塞(如调用了sleep()、wait()、join()等方法),则该线程会抛出一个 InterruptedException异常,从而提前结束阻塞状态。

java 复制代码
/**
 * @author 一一哥Sun
 */
public class Demo08 {
	public static void main(String[] args) throws InterruptedException {
		Thread t = new Thread(() -> {
			//判断当前线程是否中断了
            while (!Thread.currentThread().isInterrupted()) {
				System.out.println("Thread 正在运行...");
                try {
                	//休眠1秒
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                	// 发生异常时,中断线程
                    Thread.currentThread().interrupt(); 
                    System.out.println("Thread 中断了");
                }
            }
        });

		//开始执行
        t.start();

        // 主线程等待5秒
        Thread.sleep(5000);
        //中断t线程,如果发现线程t正在被阻塞,则t线程会抛出InterruptedException异常
        t.interrupt();
	}
}

在上面的案例中,我们开启了一个线程t,在该线程内部判断当前线程是否处于中断状态,如果没有中断则每隔1秒钟打印输出一句话。在主线程等待5秒后,线程t调用interrupt()方法进行中断,因为线程t每隔1秒会sleep()一下,所以线程t收到中断信号后就立即抛出了InterruptedException异常。

5. 优先级方法

setPriority()方法用于设置线程的优先级,getPriority()方法用于获取当前线程的优先级。优先级越高的线程在竞争CPU资源时获得的机会越大,所以就可以优先执行。Java中线程的优先级范围是1~10,其中1是最低优先级,10是最高优先级。

java 复制代码
/**
 * @author 一一哥Sun
 */
public class Demo09 {
	public static void main(String[] args) throws InterruptedException {
		// 采用Lambda表达式创建线程
		Thread t1 = new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				System.out.println("子线程1执行,i = " + i+",优先级:"+Thread.currentThread().getPriority());
			}
		});

		Thread t2 = new Thread(() -> {
			for (int i = 0; i < 10; i++) {
				System.out.println("子线程2执行,i = " + i+",优先级:"+Thread.currentThread().getPriority());
			}
		});

		// 设置线程优先级,我们可以直接使用Thread中自带的常量值
		t1.setPriority(Thread.NORM_PRIORITY);
		t2.setPriority(Thread.MAX_PRIORITY);

		t1.start();
		t2.start();
	}
}

在Thread类,有3个内置的优先级的常量:

  • Thread .*MIN_PRIORITY: *对应的值是1;
  • Thread .*NORM_PRIORITY: *对应的值是5;
  • Thread .*MAX_PRIORITY: *对应的值是10。

6. isAlive()方法

isAlive()方法用于判断当前线程是否处于活动状态,即线程是否还在执行中。该方法会返回一个布尔值,用于表示该线程是否正在运行中,如果线程已经终止或还未启动,则返回false。

当一个线程启动后,它会执行其中的run()方法。只要run()方法没有执行完毕,该线程就处于活动状态,即处于运行中。一旦run()方法执行完毕,线程就会停止运行,即不再处于活动状态。所以当线程已经启动并处于活动状态时,isAlive()方法就会返回true。如果一个线程还没有启动,或者已经执行完毕停止运行,调用isAlive()方法就会返回false

在这种情况下,如果我们需要确定一个线程是否已经停止运行,就可以使用isAlive()方法来进行判断。如果该方法返回true,说明线程还在运行;如果返回false,说明线程已经停止运行。

java 复制代码
/**
 * @author 一一哥Sun
 */
public class Demo10 {
	public static void main(String[] args) throws InterruptedException {
        MyThread2 thread = new MyThread2();
        System.out.println("Thread is alive: " + thread.isAlive()); // 输出 false
        thread.start();
        
        System.out.println("Thread is alive: " + thread.isAlive()); // 输出 true
        // 等待2秒,等待线程执行完成
        Thread.sleep(2000);
        System.out.println("Thread is alive: " + thread.isAlive()); // 输出 false
	}
}

class MyThread2 extends Thread {
    public void run() {
        System.out.println("Thread is running...");
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Thread is finished.");
    }
}

在上面的示例中,壹哥 创建了一个继承自Thread类的自定义线程MyThread2。在main方法中,壹哥首先创建了一个MyThread2对象,并打印出该线程是否处于活动状态(即是否调用了start方法)。由于线程还没有开始运行,所以isAlive()方法返回false。接着,我们调用了start()方法启动了该线程,并再次调用isAlive()方法,此时返回true,因为线程已经开始运行。最后,我们又通过Thread.sleep()方法等待2秒,等待线程执行完成,再次调用isAlive()方法,此时返回false,因为线程已经执行完毕。

7. wait()和notify()方法

wait()方法是Java中Object类提供的方法,可以使当前线程进入到等待状态,等其他线程发出唤醒通知后,该线程会继续执行。当一个线程调用了wait()方法后,它会释放当前持有的锁,进入到等待状态,直到其他线程调用了相应对象的notify()或 notifyAll()方法来唤醒它 。接下来壹哥会设计一个wait()方法的案例,实现两个线程之间的通信。

在接下来的案例中,壹哥 在Demo11这个类中定义了一个变量number和两个方法increment()和decrement()。increment()方法会增加变量number的值,而decrement()方法会减少它的值。在主方法中,壹哥会创建两个线程ThreadA和ThreadB,它们分别调用increment()和decrement()方法,并使用wait()和notify()方法实现线程之间的协作。具体就是,当变量number的值为0时,ThreadA线程调用increment()方法增加它的值,并使用notify()方法通知ThreadB线程开始执行decrement()方法。当变量number的值为1时,ThreadB线程会调用decrement()方法来减少它的值,并使用wait()方法等待ThreadA线程发来的通知,直到变量number的值再次变为0。这样,两个线程之间就可以协作地增加和减少变量number的值。

java 复制代码
/**
 * @author 一一哥Sun
 */
public class Demo11 {
	//定义一个数字
	private int number = 0;

	//数字加1
	//添加synchronized同步锁,以后壹哥再细讲synchronized的作用
	public synchronized void increment() {
		number++;
		// 完成操作后,通知其他线程可以执行了
		notify();
	}

	//数字减1
	public synchronized void decrement() throws InterruptedException {
		while (number == 0) {
			// 进入等待状态,等待其他线程的通知
			wait();
		}
		//数字减1
		number--;
	}

	public static void main(String[] args) {
		Demo11 demo = new Demo11();

		//创建A线程
		Thread threadA = new Thread(() -> {
			for (int i = 0; i < 5; i++) {
				//数字增加
				demo.increment();
				System.out.println("Thread A: " + demo.number);
			}
		});

		//创建B线程
		Thread threadB = new Thread(() -> {
			for (int i = 0; i < 5; i++) {
				try {
					//数字减少
					demo.decrement();
					System.out.println("Thread B: " + demo.number);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});

		//启动线程
		threadB.start();
		threadA.start();
	}
}

在上面的案例中,壹哥使用了synchronized关键字来保证两个方法的原子性操作,避免了线程的安全问题。同时,我们在decrement()方法中使用while循环来避免虚假的唤醒问题。当变量number为0时,ThreadB线程调用wait()方法等待ThreadA线程的通知,且只有变量number再次变为0时,才会从wait()方法中返回,继续执行decrement()方法。在这个过程中,两个线程之间通过notify()和wait()方法协作,避免了死锁和数据竞争的问题。

8. 其他方法

除了以上方法之外,还有一些其他与线程操作相关的方法,壹哥就不再一一展示对应的代码了,大家可以自行尝试一下。

比如setDaemon()方法用于设置线程是否为守护线程。守护线程是一种特殊的线程,当一个Java应用程序中只剩下守护线程时,JVM就会自动退出。因此通常情况下,我们不应该在守护线程中执行一些需要必须完成的任务,如数据存储等。

getState()方法用于获取线程的状态。线程的状态包括:NEW(新建)、RUNNABLE(可运行)、BLOCKED(阻塞)、WAITING(等待)、TIMED_WAITING(超时等待)和TERMINATED(终止) 。关于线程状态,壹哥后面再详细介绍。

getName()和setName()方法则分别用于获取和设置线程的名称。

------------------------------正片已结束,来根事后烟----------------------------

三. 结语

今天的这篇文章,壹哥 重点给大家介绍了线程中核心方法的使用,请大家多敲代码多熟悉这些方法的使用,并仔细体会这些方法的具体含义,以后这些方法我们都会经常用到。尤其是sleep()、join()、yield()、wait()、interrupt()这几个控制线程执行的方法,我们在面试时经常会被面试官问到这几个方法的区别

  • sleep()方法:使当前线程进入休眠阻塞状态,让出CPU的执行权,不会释放对象的锁,指定时间之后线程会重新进入可运行的就绪状态。常用于模拟耗时操作。
  • join()方法:使得当前线程等待指定的线程执行结束后再继续执行,常用于等待子线程执行完毕后再执行主线程。
  • yield()方法:让出当前线程占用的CPU时间,让其他线程有机会运行。与sleep()不同的是,yield()方法不会进入休眠阻塞状态,而是进入可运行的就绪状态,CPU执行权有可能被立即重新分配给该线程。常用于调试或测试多线程程序。
  • wait()方法:使当前线程开始等待,直到其他线程调用notify()或notifyAll()方法唤醒它,或者等到指定的时间之后自动唤醒。wait()方法必须在同步块或同步方法中调用,调用wait()方法会释放对象的锁。
  • interrupt()方法:用于中断线程的执行。当线程调用interrupt()方法时,该线程会收到一个中断信号,线程可以在中断信号的提示下进行一些清理工作,然后结束线程的执行。

简而言之,sleep()和yield()主要用于控制线程的运行时间和调度,join()方法主要用于等待其他线程的执行结果,wait()主要用于线程间的通信和协作。现在你明白了吗?

如果你独自学习觉得有很多困难,可以加入壹哥的学习互助群,大家一起交流学习。

相关推荐
儿时可乖了16 分钟前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
ruleslol18 分钟前
java基础概念37:正则表达式2-爬虫
java
xmh-sxh-131434 分钟前
jdk各个版本介绍
java
XINGTECODE1 小时前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
天天扭码1 小时前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶1 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺1 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端
小曲程序1 小时前
vue3 封装request请求
java·前端·typescript·vue