多线程通信和线程同步是多线程编程中的两个关键概念。线程通信指的是多个线程之间交换数据或信号,而线程同步则是指控制线程对共享资源的访问顺序,以确保数据的正确性和一致性。
1. 线程之间的通信
线程之间的通信指的是一个线程向另一个线程发送信号或数据,以便协调它们的操作。在Java中,线程通信可以通过以下几种方式实现:
1.1 使用wait(), notify(), notifyAll()方法
wait()、notify()和notifyAll()是Object类中定义的方法,通常与synchronized关键字一起使用,以实现线程之间的通信。
wait():使当前线程等待,直到另一个线程调用notify()或notifyAll()来唤醒它。调用wait()时,线程会释放它持有的锁,并进入等待状态。
notify():唤醒等待该对象监视器的一个线程。如果有多个线程在等待,那么会选择其中一个线程唤醒。注意,调用notify()后,线程并不会立即释放锁,而是要等到synchronized块执行完后才会释放锁,唤醒的线程才有机会获得锁并继续执行。
notifyAll():唤醒所有等待该对象监视器的线程,唤醒的线程将竞争锁,只有获得锁的线程才会继续执行。
class SharedResource {
private boolean condition = false;
public synchronized void waitMethod() throws InterruptedException {
while (!condition) {
wait(); // 等待条件变化
}
System.out.println("Condition met, resuming execution");
}
public synchronized void notifyMethod() {
condition = true;
notify(); // 唤醒等待线程
}
}
public class ThreadCommunicationExample {
public static void main(String[] args) throws InterruptedException {
SharedResource resource = new SharedResource();
Thread waitingThread = new Thread(() -> {
try {
resource.waitMethod();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread notifyingThread = new Thread(() -> resource.notifyMethod());
waitingThread.start();
Thread.sleep(1000); // 确保等待线程先启动
notifyingThread.start();
}
}
在这个示例中,waitingThread在waitMethod()中调用wait()方法,使其进入等待状态,而notifyingThread在notifyMethod()中调用notify()方法来唤醒等待的线程。
1.2 使用Condition对象
Condition对象提供了比wait()和notify()更细粒度的控制,并且支持多种条件。它是从Lock接口获得的,可以用来替代wait()和notify()。
await():类似于wait(),使线程等待某个条件。signal():类似于notify(),唤醒一个等待线程。signalAll():类似于notifyAll(),唤醒所有等待线程。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class SharedResourceWithCondition {
private boolean condition = false;
private final Lock lock = new ReentrantLock();
private final Condition conditionMet = lock.newCondition();
public void waitMethod() throws InterruptedException {
lock.lock();
try {
while (!condition) {
conditionMet.await();
}
System.out.println("Condition met, resuming execution");
} finally {
lock.unlock();
}
}
public void notifyMethod() {
lock.lock();
try {
condition = true;
conditionMet.signal();
} finally {
lock.unlock();
}
}
}
public class ConditionExample {
public static void main(String[] args) throws InterruptedException {
SharedResourceWithCondition resource = new SharedResourceWithCondition();
Thread waitingThread = new Thread(() -> {
try {
resource.waitMethod();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread notifyingThread = new Thread(() -> resource.notifyMethod());
waitingThread.start();
Thread.sleep(1000);
notifyingThread.start();
}
}
在这个示例中,Condition对象被用来管理线程通信,提供了更细粒度的控制。
1.3 使用BlockingQueue
BlockingQueue是Java中的一种线程安全的队列,可以在生产者-消费者模型中用于线程通信。它提供了阻塞的put()和take()方法,生产者线程可以通过put()方法向队列中添加元素,而消费者线程通过take()方法从队列中取出元素。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueExample {
public static void main(String[] args) {
BlockingQueue
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
queue.put(i);
System.out.println("Produced: " + i);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread consumer = new Thread(() -> {
try {
while (true) {
Integer value = queue.take();
System.out.println("Consumed: " + value);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
producer.start();
consumer.start();
}
}
在这个示例中,BlockingQueue用作生产者和消费者之间的通信媒介,take()方法会阻塞直到队列中有可用的元素,而put()方法会阻塞直到队列有空间来添加新元素。
2. 线程之间的同步
线程同步的目的是防止多个线程同时访问共享资源时出现数据不一致的情况。在Java中,线程同步可以通过以下几种方式实现:
2.1 使用synchronized关键字
synchronized是Java中最常用的线程同步机制,它可以用于方法或代码块来确保同一时刻只有一个线程可以执行同步代码。它通过对对象加锁来实现线程之间的互斥访问。
同步方法:当一个方法被synchronized修饰时,访问该方法的线程必须先获得对象的锁,其他线程必须等待,直到该线程释放锁。
public synchronized void increment() {
counter++;
}
同步代码块:同步代码块允许更细粒度的控制,只同步需要的代码,而不是整个方法。
public void increment() {
synchronized(this) {
counter++;
}
}
2.2 使用Lock接口
Lock接口提供了比synchronized更灵活的同步机制。ReentrantLock是Lock接口的常见实现类,它提供了显式锁定和解锁的功能。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final Lock lock = new ReentrantLock();
private int counter = 0;
public void increment() {
lock.lock();
try {
counter++;
} finally {
lock.unlock();
}
}
public int getCounter() {
return counter;
}
}
在这个示例中,ReentrantLock提供了显式的锁定和解锁机制,使得开发者可以更精细地控制锁的获取和释放。
2.3 使用Atomic类
Java提供了一系列的原子类,如AtomicInteger、AtomicLong、AtomicReference等,它们通过无锁机制实现线程安全的原子操作。这些类通常用于简单的计数器或标志位操作。
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicExample {
private final AtomicInteger counter = new AtomicInteger(0);
public void increment() {
counter.incrementAndGet();
}
public int getCounter() {
return counter.get();
}
}
原子类通过硬件级别的原子操作实现了线程安全,非常高效,适用于简单的数值或对象引用操作。
2.4 使用ReadWriteLock
ReadWriteLock是一种特殊的锁机制,它允许多个线程同时读取共享资源(读锁),但只允许一个线程写入(写锁)。这在读操作频繁、写操作较少的场景下特别有用。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockExample {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();
private int value;
public void writeValue(int newValue) {
writeLock.lock();
try {
value = newValue;
}
finally {
writeLock.unlock();
}
}
public int readValue() {
readLock.lock();
try {
return value;
} finally {
readLock.unlock();
}
}
}
在这个示例中,ReadWriteLock允许多个线程同时读取数据,而写操作需要独占锁。
2.5 使用Semaphore
信号量(Semaphore)是一种用于限制多个线程访问共享资源的同步工具。它允许多个线程同时访问资源,但可以限制访问的线程数量。
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
private final Semaphore semaphore = new Semaphore(3);
public void accessResource() {
try {
semaphore.acquire();
// 执行资源访问操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
semaphore.release();
}
}
}
在这个示例中,Semaphore限制了同时访问资源的线程数量,适用于需要控制资源访问量的场景。
3. 线程通信与同步的结合使用
在实际应用中,线程通信和线程同步通常是结合使用的。例如,在生产者-消费者模型中,生产者和消费者线程需要通过线程通信来协调工作,同时通过线程同步来保护共享队列的访问。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class ProducerConsumerExample {
private final BlockingQueue
public void produce(int value) throws InterruptedException {
queue.put(value);
System.out.println("Produced: " + value);
}
public void consume() throws InterruptedException {
int value = queue.take();
System.out.println("Consumed: " + value);
}
public static void main(String[] args) {
ProducerConsumerExample example = new ProducerConsumerExample();
Thread producer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
example.produce(i);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
Thread consumer = new Thread(() -> {
try {
for (int i = 0; i < 10; i++) {
example.consume();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
producer.start();
consumer.start();
}
}
在这个例子中,生产者和消费者通过BlockingQueue进行通信,并利用其内置的同步机制确保线程安全。
4. 总结
线程之间的通信和同步是Java多线程编程中的核心概念。通过适当使用wait()、notify()、BlockingQueue、synchronized、Lock接口、原子类、ReadWriteLock和Semaphore,可以实现线程之间的高效通信和安全同步。