Java多线程——线程基础
前言
线程是Java中不可或缺的重要功能,它们能使复杂的异步代码变得更简单,从而极大地简化了复杂系统的开发。此外,要想充分发挥多处理器系统的强大计算能力,最简单的方式就是使用多线程。随着现在硬件中处理器数量的持续增长,高效地编写并发程序变得越来越重要。
在学习多线程中,发现B站上黑马程序员讲解多线程的视频非常棒,推荐给大家:
一、进程和线程
进程和线程是老生常谈的一个问题,进程是资源分配的基本单位,线程是CPU调度的基本单位,每一个程序就是一个进程,而一个进程里面会包含多个线程。关于进程和线程的更多介绍可以参考进程和线程
二、Java创建线程
在Java中创建线程有三种方式:
- 方法一:直接使用Thread
@Slf4j
public class Demo01 {
public static void main(String[] args) {
Thread t1 = new Thread() {
@Override
public void run() {
log.info("This is thread t1");
}
};
t1.start();
}
}
这种方法耦合度比较高,每个Thread都要重写里面的run方法
- 方法二:使用Runnable配合Thread
把线程
和内容
分割开来
Runnable
表示可运行的内容
Thread
表示线程
@Slf4j
public class Demo01 {
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
log.info("This is thread t2");
}
};
new Thread(runnable).start();
}
}
- 方法三:使用FutureTask配合Thread
FutureTask
类
可以看到FutureTask
类实现了Runnable
接口,FutureTask
能够接受Callable
参数,从而获得线程返回值
// Callable是一个函数式接口
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
@Slf4j
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<>(() -> {
log.info("This is futureTask");
return 46;
});
new Thread(task).start();
Integer result = task.get();
log.info("result: {}", result.toString());
}
}
三、线程状态
线程状态是线程的重要属性,Java线程状态一般有两种划分方法:五种和六种
3.1 五种状态
线程的五种状态:
- 新建:刚刚创建出来的线程的状态
- 就绪:线程分到资源,等待CPU的调用,但还没运行
- 运行:就绪的线程分到了CPU,正在运行
- 阻塞:线程没有分到资源运行代码,进入阻塞状态
- 终结:线程异常终端或者全部执行完毕
3.2 六种状态
五种状态是在操作系统
的层次进行定义的,而且JDK中对线程状态的定义
// 线程状态类
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
- 新建:使用
new
关键字创建了一个thread
对象 - 运行:包含正在运行的线程和等待CPU调度的线程
- 阻塞:在运行状态
争夺锁失败
的线程会进入阻塞状态 - 等待:在运行状态
争夺锁成功
,但资源不满足
,主动放弃锁进入等待状态 - 超时等待:在等待时有一定
时限
,超过这个时间进入可运行状态 - 终止:代码执行完毕后,释放所有资源,线程进入终止状态
四、线程常用方法
Thread类中有许多常用方法:
方法 | 用处 |
---|---|
start() | 启动一个新线程,start方法会让线程进入就绪状态,还需要等待CPU分配时间片才可以运行 |
join() | 等待线程运行结束 |
join(long n) | 等待线程运行结束,最多等待n毫秒 |
getId() | 获取线程Id |
getName() | 获取线程名称 |
setName() | 获取线程名称 |
getPriority() | 获取线程权限 |
setPriority() | 设置线程权限 |
getState() | 获取线程状态 |
isInterrupted() | 判断是否被打断,不会清除打断标记 |
isAlive() | 线程还是否存活 |
interrupt() | 打断线程 |
interrupted() | 判断当前线程是否被打断,会清除打断标记 |
currentThread() | 获取当前正在执行的线程 |
sleep(long n) | 线程休眠n毫秒 |
yield() | 提醒线程调度器让出当前线程对CPU的使用 |
4.1 start和run方法
- 直接调用
run
是在主线程中执行run
方法,没有启动新的线程 - 使用
start
是启动了新的线程,通过新的线程间接执行run
中的代码
直接调用run
@Slf4j
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.info("hello run");
}
};
t1.run();
}
/**
* 2022-11-25 11:06:41.553 INFO [main] - hello run
*/
}
调用start
方法
@Slf4j
public class Demo01 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Thread t1 = new Thread("t1") {
@Override
public void run() {
log.info("hello run");
}
};
t1.start();
}
/**
* 2022-11-25 11:08:06.065 INFO [t1] - hello run
*/
}
4.2 sleep和yield方法
sleep
- 调用
sleep
会让当前线程从Running进入Timed Waiting状态 - 其他线程可以使用
interrupt
方法打断正在睡眠的线程,这时sleep
方法会抛出InterruptedException
- 睡眠结束后的线程也不一定会立刻得到执行
yield
yield
方法可以让当前线程让出cpu,当前线程从Running状态进入Runnable就绪状态
线程优先级
- 线程可以设置执行优先级,优先级会在线程调度的时候提示调度器
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
- JDK中优先级范围1-10,默认优先级为5
4.3 join方法
当前线程需要等待其他线程执行时,可以调用join
方法
@Slf4j
public class Demo02 {
public static void main(String[] args) throws InterruptedException {
log.info("主线程开始");
Thread t1 = new Thread(() -> {
log.info("t1线程开始");
// 线程睡眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
log.info("t1线程执行结束");
}, "t1");
t1.start();
// 等待t1线程执行结束
t1.join();
log.info("主线程结束");
}
/**
* 2022-11-25 11:24:18.101 INFO [main] - 主线程开始
* 2022-11-25 11:24:18.206 INFO [t1] - t1线程开始
* 2022-11-25 11:24:19.212 INFO [t1] - t1线程执行结束
* 2022-11-25 11:24:19.213 INFO [main] - 主线程结束
*/
}
4.4 interrupt方法
打断sleep
、wait
、join
的线程
@Slf4j
public class Demo03 {
private static void test01() {
Thread t1 = new Thread(() -> {
log.info("t1线程开始");
// 线程睡眠1秒
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
log.info("线程被打断");
}
log.info("t1线程执行结束");
}, "t1");
t1.start();
t1.interrupt();
log.info("打断: {}", t1.interrupted());
}
/**
* 2022-11-25 13:01:09.105 INFO [t1] - t1线程开始
* 2022-11-25 13:01:09.109 INFO [t1] - 线程被打断
* 2022-11-25 13:01:09.109 INFO [t1] - t1线程执行结束
* 2022-11-25 13:01:09.113 INFO [main] - 打断: false
*/
private static void test02() {
Thread t2 = new Thread(() -> {
log.info("t2线程开始");
while (true) {
Thread thread = Thread.currentThread();
boolean interrupted = thread.interrupted();
if (interrupted) {
log.info("线程打断状态: {}", interrupted);
}
}
}, "t2");
t2.start();
t2.interrupt();
}
/**
* 2022-11-25 13:00:42.959 INFO [t2] - t2线程开始
* 2022-11-25 13:00:42.969 INFO [t2] - 线程打断状态: true
*/
}
应用:两阶段终止
即在一个线程中终止另一个线程,并给另一个线程释放资源或保存数据的时间
@Slf4j
public class TPTInterrupt {
private Thread thread;
public void start() {
thread = new Thread(() -> {
while (true) {
Thread current = Thread.currentThread();
if (current.isInterrupted()) {
log.info("释放资源");
break;
}
try {
Thread.sleep(1000);
log.info("将结果保存");
} catch (InterruptedException e) {
// 在sleep阶段被打断时候,会清除打断标记,需要再次打断
current.interrupt();
log.info("线程打断");
}
}
}, "监控线程");
thread.start();
}
public void stop() {
thread.interrupt();
}
public static void main(String[] args) throws InterruptedException {
TPTInterrupt t = new TPTInterrupt();
t.start();
Thread.sleep(3500);
log.info("stop");
t.stop();
}
}
4.5 守护线程
守护线程
是操作系统中的一个概念,又被称为“服务线程”或“后台线程”,是指在程序运行时在后台提供一种通用服务的线程,如JVM中的垃圾清理就是一个守护线程。
守护线程
和用户线程
的一个重要区别是:当用户线程没有全部运行结束时,程序不会停止;当用户线程全部运行完,只剩守护线程时,程序会终止。
在Java中可以通过thread.isDaemon()
来判断线程是否是守护线程,并可以通过thread.setDaemon(true)
来将线程设置为守护线程。
总结
本文简略介绍了线程的基础知识,这是学习Java多线程编程的基础。