Java中线程之间是如何通信的
在 Java 中,线程之间的通信是通过共享内存模型来实现的,线程通过共享的对象和变量来交换数据。为了确保线程间通信的正确性,Java 提供了一系列机制来实现线程安全、同步和通信。以下是常用的几种线程间通信的方式,以及它们的使用方法和场景。
1. 共享变量与同步机制
多个线程可以通过共享对象的变量进行通信,但为了避免数据不一致的问题,必须使用同步机制来控制对共享变量的访问。
-
使用
synchronized
关键字:synchronized
确保在同一时刻只有一个线程可以执行同步代码块。它可以同步方法或代码块,用于保护共享数据。示例:
class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } public class SyncExample { public static void main(String[] args) { 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(); try { t1.join(); t2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Final count: " + counter.getCount()); } }
使用场景:用于保护共享资源,防止多个线程同时读写导致数据不一致的问题。
-
使用
volatile
关键字:volatile
确保一个线程对变量的修改对其他线程立即可见。它适用于轻量级的变量同步,通常用于标志位控制。示例:
class Flag { private volatile boolean running = true; public void stop() { running = false; } public boolean isRunning() { return running; } } public class VolatileExample { public static void main(String[] args) { Flag flag = new Flag(); Thread t1 = new Thread(() -> { while (flag.isRunning()) { System.out.println("Thread is running"); } System.out.println("Thread stopped"); }); t1.start(); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } flag.stop(); } }
使用场景:用于控制线程的退出、开关操作等轻量级场景。
2. wait()
、notify()
和 notifyAll()
Object
类的这三个方法用于在同步块内实现线程间的协作。线程可以通过 wait()
进入等待状态,直到另一个线程调用 notify()
或 notifyAll()
唤醒它们。
wait()
:当前线程进入等待状态,并释放锁。notify()
:唤醒一个正在等待同一锁的线程。notifyAll()
:唤醒所有等待同一锁的线程。
示例:生产者-消费者模型
class SharedResource {
private int value;
private boolean hasValue = false;
public synchronized void produce(int newValue) throws InterruptedException {
while (hasValue) {
wait(); // 等待消费者消费
}
value = newValue;
hasValue = true;
System.out.println("Produced: " + value);
notify(); // 唤醒消费者
}
public synchronized void consume() throws InterruptedException {
while (!hasValue) {
wait(); // 等待生产者生产
}
System.out.println("Consumed: " + value);
hasValue = false;
notify(); // 唤醒生产者
}
}
public class ProducerConsumerExample {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
resource.produce(i);
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
resource.consume();
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
使用场景:适用于线程间的协调工作,比如生产者-消费者模型,一个线程负责生产资源,另一个线程负责消费资源。
3. Lock
和 Condition
接口
相比 synchronized
,Lock
提供了更灵活的同步机制,而 Condition
可以替代 wait()
、notify()
和 notifyAll()
,并支持多个等待条件。
ReentrantLock
:常用于显式锁控制,可以提供公平锁机制(按获取锁的顺序进行调度)。Condition
:类似于Object
的wait()
、notify()
,可以创建多个Condition
来进行复杂的线程协调。
示例:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class BoundedBuffer {
private final int[] buffer;
private int count, in, out;
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();
public BoundedBuffer(int size) {
buffer = new int[size];
}
public void put(int value) throws InterruptedException {
lock.lock();
try {
while (count == buffer.length) {
notFull.await(); // 等待缓冲区未满
}
buffer[in] = value;
in = (in + 1) % buffer.length;
count++;
notEmpty.signal(); // 唤醒消费线程
} finally {
lock.unlock();
}
}
public int take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await(); // 等待缓冲区非空
}
int value = buffer[out];
out = (out + 1) % buffer.length;
count--;
notFull.signal(); // 唤醒生产线程
return value;
} finally {
lock.unlock();
}
}
}
public class LockConditionExample {
public static void main(String[] args) {
BoundedBuffer buffer = new BoundedBuffer(5);
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
buffer.put(i);
System.out.println("Produced: " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread consumer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
int value = buffer.take();
System.out.println("Consumed: " + value);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
producer.start();
consumer.start();
}
}
使用场景:适用于需要显式锁定、更复杂的条件等待场景,比如多条件同步、多生产者-消费者模型。
4. java.util.concurrent
包的并发工具
Java 提供了 java.util.concurrent
包下的一系列高级并发工具类来简化线程间通信。
- BlockingQueue:线程安全的队列,常用于生产者-消费者模式。
- CountDownLatch:允许一个或多个线程等待其他线程完成某些操作。
- CyclicBarrier:多个线程在某个点上相互等待,直到所有线程到达该点。
- Semaphore:用于控制对资源的访问许可。
- Exchanger:用于两个线程之间交换数据。