Java 的多线程是一种允许在一个程序中同时运行多个线程的技术。每个线程是独立的执行路径,可以并发执行,从而提高程序的效率和响应能力。
1. 线程基础
Java 中的线程可以通过继承 Thread
类或实现 Runnable
接口来创建和管理。
1.1 继承 Thread
类
class MyThread extends Thread {
public void run() {
System.out.println("Thread is running...");
}
}
public class TestThread {
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // 启动线程
}
}
run()
方法包含线程执行的代码,而 start()
方法用于启动新线程。
1.2 实现 Runnable
接口
class MyRunnable implements Runnable {
public void run() {
System.out.println("Thread is running...");
}
}
public class TestRunnable {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread t1 = new Thread(myRunnable);
t1.start(); // 启动线程
}
}
通过实现 Runnable
接口的方式可以更灵活地共享资源。
2. 线程的生命周期
线程在其生命周期中经历以下状态:
- 新建(New): 线程对象被创建,但还没有调用
start()
方法。 - 就绪(Runnable): 线程对象调用了
start()
方法,等待 CPU 调度。 - 运行(Running): 线程获得 CPU,开始执行
run()
方法的代码。 - 阻塞(Blocked): 线程因为某种原因(如等待资源、睡眠)被挂起。
- 死亡(Dead): 线程执行完
run()
方法,或者因异常退出。
3. 线程控制
Java 提供了一些方法来控制线程的执行:
sleep(long millis)
:让当前线程睡眠指定的毫秒数。join()
:等待该线程终止,也就是说等待当前的线程结束, 才会继续执行下面的代码yield()
:暂停当前线程,让出 CPU 给其他线程。interrupt()
:中断线程。
4. 线程同步
多线程程序中可能会出现多个线程同时访问共享资源的情况,导致数据不一致的问题。为了解决这个问题,可以使用同步技术。
4.1. ReentrantLock
ReentrantLock
提供了比 synchronized
更加灵活的锁机制,可以显式地锁定和解锁。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
4.2. ReadWriteLock
ReadWriteLock
提供了一对锁,一个用于读操作,一个用于写操作。这允许多个读线程同时访问共享资源,但在写线程访问时会独占锁。
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReadWriteLock lock = new ReentrantReadWriteLock();
private int count = 0;
public void increment() {
lock.writeLock().lock();
try {
count++;
} finally {
lock.writeLock().unlock();
}
}
public int getCount() {
lock.readLock().lock();
try {
return count;
} finally {
lock.readLock().unlock();
}
}
}
4.3 synchronized
在方法前使用 synchronized
关键字,确保同一时间只有一个线程可以执行该方法。
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
4.4 同步块
同步块可以更灵活地控制需要同步的代码块,而不是整个方法。
class Counter {
private int count = 0;
public void increment() {
synchronized (this) {
count++;
}
}
public int getCount() {
return count;
}
}
4.5 注意事项
下面代码中的写法, 不能保证同一时间只有一个线程可以执行该方法。
因为 4.3 和 4.4 中 synchronized
的写法是根据 this
来保证同一时间只有一个线程可以执行, 但是他们的 this
是不同的。(把 “需要替换的” 换成注释上的就可以 “保证同一时间只有一个线程可以执行”)
class Worker implements Runnable {
public static int cnt = 0;
@Override
public synchronized void run() {
for (int i = 0; i < 100000; i ++) {
cnt ++;
}
}
}
public class Main {
public static void main(String[] args) throws InterruptedException {
// Worker worker = new Worker();
// Thread t1 = new Thread(worker);
// Thread t2 = new Thread(worker);
Thread t1 = new Thread(new Worker()); // 需要替换
Thread t2 = new Thread(new Worker()); // 需要替换
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(Worker.cnt);
}
}
当然, 4.4 的代码也可以写成下面这样,这样他就是根据 lock
这个对象来保证同步的, java中的对象都可以当作lock
class Counter {
public static final Object lock = new Object();
private int count = 0;
public void increment() {
synchronized (lock ) {
count++;
}
}
public int getCount() {
return count;
}
}
5. 线程通信
Java 提供了 wait()
, notify()
, notifyAll()
方法来实现线程间的通信。
wait()
:让当前线程等待,直到其他线程调用notify()
或notifyAll()
。notify()
:唤醒一个正在等待的线程。notifyAll()
:唤醒所有正在等待的线程。
6. 线程池
使用线程池可以有效地管理和复用线程,减少创建和销毁线程的开销。Java 提供了 Executor
框架来管理线程池。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class TestThreadPool {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executor.execute(new MyRunnable());
}
executor.shutdown();
}
}
7. 高级线程工具
Java 提供了很多高级线程工具,如 Semaphore
, CountDownLatch
, CyclicBarrier
等,用于复杂的线程协调和同步。
7.1 Semaphore
信号量控制同时访问特定资源的线程数量。
import java.util.concurrent.Semaphore;
public class TestSemaphore {
private static final Semaphore semaphore = new Semaphore(3);
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new Task()).start();
}
}
static class Task implements Runnable {
public void run() {
try {
semaphore.acquire();
System.out.println("Thread " + Thread.currentThread().getName() + " is accessing the resource.");
Thread.sleep(2000);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
7.2 CountDownLatch
允许一个或多个线程等待,直到在其他线程中执行的一组操作完成。
import java.util.concurrent.CountDownLatch;
public class TestCountDownLatch {
private static final int THREAD_COUNT = 3;
private static final CountDownLatch latch = new CountDownLatch(THREAD_COUNT);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(new Task()).start();
}
try {
latch.await(); // 主线程等待所有子线程完成
System.out.println("All threads have finished.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class Task implements Runnable {
public void run() {
System.out.println("Thread " + Thread.currentThread().getName() + " is working.");
latch.countDown(); // 计数器减一
}
}
}
结论
Java 多线程是一个强大且复杂的技术,需要深入理解和小心使用,以避免潜在的并发问题和死锁情况。通过合理地利用线程同步、线程通信和线程池等工具,可以编写高效且安全的多线程应用程序。