一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第28天,点击查看活动详情。
线程的创建和使用
1.Thread类
Java语言的JVM允许程序运行多个线程,它通过java.lang.Thread 类来体现。
1.1Thread类的特性
- 每个线程都是通过某个特定Thread对象的run()方法来完成操作的,经常 把run()方法的主体称为线程体
- 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
1.2构造器
- Thread():创建新的Thread对象
- Thread(String threadname):创建线程并指定线程实例名
- Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接 口中的run方法
- Thread(Runnable target, String name):创建新的Thread对象
1.3Thread类的有关方法
- void start(): 启动线程,并执行对象的run()方法
- run(): 线程在被调度时执行的操作
- String getName(): 返回线程的名称
- void setName(String name):设置该线程名称
- static Thread currentThread(): 返回当前线程。在Thread子类中就 是this,通常用于主线程和Runnable实现类
- static void yield():线程让步
- 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程 若队列中没有同优先级的线程,忽略此方法
- join() :当某个程序执行流中调用其他线程的 join() 方法时,调用线程将 被阻塞,直到 join() 方法加入的 join 线程执行完为止( 低优先级的线程也可以获得执行 )
- static void sleep(long millis):(指定时间:毫秒)令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后 重排队。抛出InterruptedException异常
- stop(): 强制线程生命期结束,不推荐使用
- boolean isAlive():返回boolean,判断线程是否还活着
1.4线程的优先级
1.4.1线程的优先等级
- MAX_PRIORITY:10
- MIN _PRIORITY:1
- NORM_PRIORITY:5 //默认
2.如何获取和设置当前线程的优先级
- getPriority();//获取
- setPriority(int p);//设置
3.说明:
- 线程的优先级并不意味着优先级高的一定要在优先级低的线程之前执行。
- Thread类的常用方法
- 1.start():启动当前线程;调用当前线程的run()方法
- 2.run():通常需要重写Thread类中的此方法,将新建线程所需执行的操作声明在此方法中
- 3.currentThread():静态方法,返回执行当前代码的线程
- 4.getName():获取当前线程的名字
- 5.setName():设置当前线程的名字 或者调用Thread的带参构造器设置线程名
- 6.yield():释放当前cpu的执行权
- 7.join():在线程A中调用线程B的join(),此时线程A进入阻塞状态,直到线程B完全执行完,线程A结束阻塞状态
- 8.stop():强制结束线程的生命周期,不建议使用。
- 9.sleep(long millis):让当前线程“睡眠”指定的millis毫秒数。在指定的时间内,当前线程时阻塞状态
- 10.isAlive():判断当前线程是否存活。
- 线程的优先级
-
MAX_PRIORITY:10
-
MIN _PRIORITY:1
-
NORM_PRIORITY:5 //默认
-
- 2.如何获取和设置当前线程的优先级
- getPriority();//获取
- setPriority(int p);//设置
- 3.说明:线程的优先级并不意味着优先级高的一定要在优先级低的线程之前执行。
2.创建线程方式一:继承Thread类
2.1创建流程
- 定义子类继承Thread类。
- 子类中重写Thread类中的run方法。
- 创建Thread子类对象,即创建了线程对象。
- 调用线程对象start方法:启动线程,调用run方法。
package taiacloud.java;
/**
* 多线程的创建
* 方式一:继承Thread类
* 1.创建一个继承与Thread类的子类
* 2.重写Thread类的run()方法 --> 将此线程执行的操作声明到run()方法中
* 3.创建Thread类的子类的对象
* 4.通过此对象调用start()方法:1.启动当前线程 2.调用当前线程的run方法
*
* 例子:遍历100以内的所有偶数
*
*
*
*
* @author taia
* @creat 2021-10-12-10:04
*/
//1.创建一个继承与Thread类的子类
class MyThread extends Thread {
//2.重写Thread类的run()方法
@Override
public void run() {
// 例子:遍历100以内的所有偶数
for (int i = 0; i < 100; i++) {
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
// 3.创建Thread类的子类的对象
MyThread myThread = new MyThread();
//4.通过此对象调用start()方法
myThread.start();
// myThread.run();
//问题一:直接调用run()方法执行的仍然是main线程,不会新建线程
//问题二:再启动一个线程,遍历100以内的偶数。一个线程不可以重复调用。
// 创建新线程需要新建对象调用start()
MyThread myThread1 = new MyThread();
myThread1.start();
//如下操作依然是在main线程中执行的
for (int i = 0; i < 100; i++) {
if (i % 2 == 0)
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
2.2注意:
- 如果自己手动调用run()方法,那么就只是普通方法,没有启动多线程模式。
- run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU 调度决定。
- 想要启动多线程,必须调用start方法。
- 一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上 的异常“IllegalThreadStateException”。
3.创建线程方式二:实现Runnable接口
3.1创建流程
- 定义子类,实现Runnable接口。
- 子类中重写Runnable接口中的run方法。
- 通过Thread类含参构造器创建线程对象。
- 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
- 调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
package taiacloud.java;
/**
* 创建多线程的方式二:实现Runnable接口
* 1.创建一个实现了Runnable接口的类
* 2.实现类去实现Runnable中的抽象方法:run()
* 3.创建此实现类的对象
* 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5.通过Thread类的对象调用start() -->启动线程 -->调用当前线程的run() -->调用了Runnable类型的target的run()
*
* 比较线程的两种创建方式:开发中,优先选择实现Runnable接口的方式
* 原因:
* 1.实现的方式没有类的单继承的局限性
* 2.实现的方式更适合来处理多个线程有共享数据的情况
*
* 联系:
* Thread类本身也是通过实现Runnable接口来创建线程的,public class Thread implements Runnable
* 相同点:
* 两种方式都需要重写run方法,将线程要执行的逻辑声明到run()方法中
*
* @author taia
* @creat 2021-10-12-18:02
*/
//1.创建一个实现了Runnable接口的类
class MThread implements Runnable{
//2.实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
public class ThreadTest2 {
public static void main(String[] args) {
//3.创建此实现类的对象
MThread mThread = new MThread();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread t1 = new Thread(mThread);
// 5.通过Thread类的对象调用start() -->启动线程 -->调用当前线程的run() -->调用了Runnable类型的target的run()
t1.start();
//再启动一个线程,遍历100以内的偶数
Thread t2 = new Thread(mThread);
t2.start();
}
}
3.2继承方式与实现方式的异同
public class Thread extends Object implements Runnable
3.2.1区别
- 继承Thread:线程代码存放Thread子类run方法中。
- 实现Runnable:线程代码存在接口的子类的run方法。
3.2.2实现方式的好处
- 避免了单继承的局限性
- 多个线程可以共享同一个接口实现类的对象,非常适合多个相同线 程来处理同一份资源
4.创建线程方式三:实现Callable接口
4.1创建流程
package com.taiacloud.java2;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建线程的方式三:实现Callable接口。---JDK5.0新增
*
* 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建的线程的方式强大?
* 1.call()可以有返回值的。
* 2.call()可以抛出异常,被外面的操作捕获,获取异常的信息
* 3.Callable是支持泛型的
*
* @author taia
* @creat 2021-10-14-15:53
*/
//1.创建一个实现Callable接口的实现类
class NumThread implements Callable{
//2.实现call方法,将此线程需要执行的操作声明在call方法中,同时call方法是可以有返回值的
@Override
public Object call() throws Exception {
int sum = 0;
//遍历100以内的偶数并返回所有偶数的和
for (int i = 1; i <= 100; i++) {
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4.将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numThread);
//5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread的对象,并调用start()方法
new Thread(futureTask).start();
//6.获取Callable中call()方法的返回值
try {
//get()返回值即为FutureTask构造器参数Callable实现类重写的call方法的返回值
Object sum = futureTask.get();
System.out.println("总和为:" + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
4.2与使用Runnable相比
Callable功能更强大些
- 相比run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
4.3Future接口
- 可以对具体Runnable、Callable任务的执行结果进行取消、查询是 否完成、获取结果等。
- FutrueTask是Futrue接口的唯一的实现类
- FutureTask 同时实现了Runnable, Future接口。它既可以作为 Runnable被线程执行,又可以作为Future得到Callable的返回值