JavaEE 多线程第四节 (线程核心操作----线程开始/线程终止)
目录
1. start() 和 run() :
总结:
2. 线程的终止控制
例子
2. lambda表达式中的变量捕获
例子
3. 为什么需要“有效final”
总结
1. start()
和 run()
:
start()
方法:- 负责通知 JVM 和操作系统创建新线程。
- 新线程被调度时,会调用
run()
方法,线程的任务会在新线程中运行。 - 只能调用一次,重复调用会抛出
IllegalThreadStateException
异常。
Thread t = new Thread(() -> {
System.out.println("Thread is running");
});
// 正确的用法,启动新线程
t.start();
// 错误的用法,重新启动线程会抛出 IllegalThreadStateException
t.start(); // 抛出异常
run()
方法:- 定义线程的实际执行内容,但不会创建新线程。
- 如果直接调用
run()
,它只是在当前线程中执行代码,而不创建新的执行线程
Thread t = new Thread(() -> {
System.out.println("Thread is running");
});
// 错误:这只是普通的方法调用,没有创建新线程
t.run(); // 直接在主线程执行,没有创建新的线程
总结:
start()
是启动线程的正确方法,它调用run()
来执行任务,并通过底层系统 API 创建真正的操作系统级线程。- 不能在同一个线程对象上调用
start()
两次,否则会抛出IllegalThreadStateException
。 run()
方法可以被直接调用,但它不会启动新线程,而是在当前线程执行任务。
2. 线程的终止控制
在Java中,主线程和子线程之间的协作是重要的。比如在一个子线程中运行一个无限循环,当某个条件满足时终止这个循环。常用的控制方式是通过一个共享变量,让主线程更新这个变量,然后子线程检查变量值来决定是否退出。
例子
public class Demo01 {
private static boolean isQuit = false;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (!isQuit) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("子线程结束");
});
t.start();
// 主线程等待2秒后,将isQuit置为true
Thread.sleep(2000);
isQuit = true;
System.out.println("主线程设置isQuit=true,通知子线程结束");
}
}
在这个例子中,isQuit
变量被主线程和子线程共享。主线程在2秒后将isQuit
置为true
,从而通知子线程结束循环。这种方法简单直接,但在多线程环境中需要确保isQuit
变量的可见性(即使用volatile
关键字或其他同步手段)。
2. lambda
表达式中的变量捕获
在Java中,lambda
表达式中引用的外部变量必须是**"有效final"**的(即变量的值在lambda表达式内部不能更改)。这个限制主要是因为lambda
表达式会将变量“捕获”到内部类的作用域中去,而该变量必须是不可变的。
例子
在以下代码中,如果将共享变量isQuit
直接放入lambda表达式中,必须确保其不可变或使用final
修饰。
public class Demo02 {
private static boolean isQuit = false;
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (!isQuit) {
System.out.println("hello");
}
});
t.start();
}
}
如果在lambda
表达式中修改了isQuit
的值,则会报错,因为Java不允许在lambda
中修改捕获的非final变量。
3. 为什么需要“有效final”
在Java的并发编程中,lambda
表达式和匿名类具有闭包特性,这意味着它们可以引用外部作用域的变量,但变量必须是不可变的。这背后的原因包括以下几点:
- 线程安全:不可变的变量在并发情况下是线程安全的。
- 内存模型:Java的内存模型对线程间的变量可见性有严格要求,不可变变量可以保证不会被其他线程修改,从而简化了JVM的实现。
总结
- 使用
isQuit
控制线程的退出,主线程可以在某个时间点设置isQuit
为true
,从而通知子线程退出。 lambda
表达式中捕获外部变量时需要保证变量是不可变的,即有效final
。- 使用
volatile
可以保证多线程环境中变量的可见性