1、Java 线程(Thread)
线程允许程序通过同时执行多项操作来更有效地运行。
线程可用于在后台执行复杂的任务,而不会中断主程序。
2、创建线程Thread
Java 中,线程(Thread)是程序执行的最小单位,Java 提供了强大的并发编程支持,可以通过 继承 Thread 类 或 实现 Runnable 接口 来创建线程。
可以通过扩展Thread
类并覆盖其run()
方法来创建它:
1)继承 Thread 类
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // 启动线程
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在运行");
}
}
注意: 不能直接调用 run(
) 方法,而应使用 start()
让 JVM 创建新线程并调用 run()
。
2)实现 Runnable 接口
public class Main {
public static void main(String[] args) {
Thread t1 = new Thread(new MyRunnable());
t1.start();
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程 " + Thread.currentThread().getName() + " 正在运行");
}
}
优点:比继承 Thread
更灵活,因为 Java 只支持单继承,而 Runnable
接口可以被多个类实现。
3)使用 Lambda 表达式
public class Main {
public static void main(String[] args) {
Thread t1 = new Thread(() -> System.out.println("线程 " + Thread.currentThread().getName() + " 正在运行"));
t1.start();
}
}
3、运行线程
如果该类使用extends
扩展了Thread
类,则可以通过创建该类的实例并调用其start()
方法来运行该线程:
例如:
public class Main extends Thread {
public static void main(String[] args) {
Main thread = new Main();
thread.start();
System.out.println("thread线程外运行");
}
public void run() {
System.out.println("thread线程中运行");
}
}
如果该类使用implements
实现了Runnable
接口,则可以通过将类的实例传递给Thread
对象的构造函数,然后调用该线程的start()
方法:
例如:
public class Main implements Runnable {
public static void main(String[] args) {
Main obj = new Main();
Thread thread = new Thread(obj);
thread.start();
System.out.println("thread线程外运行");
}
public void run() {
System.out.println("thread线程中运行");
}
}
使用extends
的线程与使用implements
的线程之间的区别主要区别在于,当一个类扩展Thread
类时,不能扩展任何其他类,但是通过实现Runnable
接口,可以从 另一个类,例如:classMyClass
扩展实现了Runnable
接的OtherClass
。
4、线程的方法
方法 | 作用 |
---|---|
start() | 启动线程并调用 run() |
run() | 线程执行的逻辑 |
join() | 等待当前线程执行完毕 |
sleep(ms) | 让当前线程睡眠一段时间 |
yield() | 让出 CPU 资源,让其他线程运行 |
interrupt() | 中断线程 |
join()
让主线程等待子线程执行完毕:
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(2000);
System.out.println("子线程执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t1.join(); // 主线程等待 t1 执行完
System.out.println("主线程继续执行");
}
}
5、线程并发问题
因为线程与程序的其他部分同时运行,所以无法知道代码将以什么顺序运行。当线程和主程序正在读取和写入相同的变量时,这些值是不可预测的。由此产生的问题称为并发问题。
例如:
一个代码示例,其中amount的值不可预测:
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
// 更新输出值
System.out.println("Main begin: " + MyThread.amount);
MyThread.amount++;
System.out.println("Main end: " + MyThread.amount);
}
}
class MyThread extends Thread {
public static int amount = 0;
public void run() {
amount++;
}
}
为避免并发问题,最好在线程之间共享尽可能少的属性。 如果需要共享属性,则一种可能的解决方案是使用线程的isAlive()
方法在使用任何可以更改的属性之前检查线程是否已完成运行。
例如:
使用isAlive()
防止并发问题:
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
// 等待线程运行完成
while(thread.isAlive()) {
System.out.println("Waiting...");
}
// 更新输出值
System.out.println("Main begin: " + MyThread.amount);
MyThread.amount++;
System.out.println("Main end: " + MyThread.amount);
}
}
class MyThread extends Thread {
public static int amount = 0;
public void run() {
amount++;
}
}
另外还可以加锁的方式,解决并发问题,例如,Java中线程的共享互斥操作,会使用synchronized
关键字。每个锁在同一时刻,只能由一个线程持有。例如,
public class Main {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
// 更新并输出值
synchronized (MyThread.class) {
System.out.println("Main begin: " + MyThread.amount);
MyThread.amount++;
System.out.println("Main end: " + MyThread.amount);
}
}
}
class MyThread extends Thread {
public static int amount = 0;
public void run() {
synchronized (MyThread.class) {
amount++;
}
}
}
注意:synchronized
方法或声明执行期间,如程序遇到任何异常或return
,线程都会释放锁。
ReentrantLock
提供了更灵活的锁机制,支持可重入、超时等待、尝试锁定等功能。
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
counter.increment();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("最终计数值: " + counter.getCount());
}
}
class Counter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}