Appearance
线程交替打印
问题:如何让两个线程交替打印1-100的数
输出例子:
Thread1 —— 1
Thread2 —— 2
Thread1 —— 3
Thread2 —— 4
方法一:使用wait()
和notify()
机制
在Java中,可以利用对象的wait()
和notify()
方法来实现线程间的协调。通过在同步代码块中使用这两个方法,可以让线程在适当的时机等待或唤醒,从而实现交替打印的效果。
实例代码:
java
public class PrintNum implements Runnable {
private int num = 1;
@Override
public void run() {
synchronized (this) {
while (num <= 100) {
notify();
System.out.println(Thread.currentThread().getName() + ": " + num);
num++;
try {
if (num <= 100) {
wait();
} else {
notify();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
public class Main {
public static void main(String[] args) {
PrintNum printNum = new PrintNum();
Thread t1 = new Thread(printNum, "Thread1");
Thread t2 = new Thread(printNum, "Thread2");
t1.start();
t2.start();
}
}
方法二:使用Lock
和Condition
Java的Lock
接口及其实现类ReentrantLock
提供了更灵活的线程同步控制。配合Condition
接口,可以实现类似wait()
和notify()
的功能,但控制更加精细。
实例代码:
java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PrintNum2 {
private int num = 1;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public static void main(String[] args) {
PrintNum2 ap = new PrintNum2();
new Thread(ap.new PrintTask(), "Thread1").start();
new Thread(ap.new PrintTask(), "Thread2").start();
}
private class PrintTask implements Runnable {
@Override
public void run() {
while (num <= 100) {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + ": " + num);
num++;
condition.signal();
if (num <= 100) {
condition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
}
区分
在Java中,wait()
和notify()
方法与ReentrantLock
和Condition
机制都用于线程间的协调与通信,但它们在使用方式和功能特性上存在一些区别。
1. 所属类别与使用方式
wait()
和notify()
方法:这两个方法是Object
类的成员,必须在同步块或同步方法中使用,即需要先获得对象的监视器锁(通过synchronized
关键字)后才能调用。ReentrantLock
和Condition
机制:ReentrantLock
是Lock
接口的实现类,提供了显式的锁操作。Condition
接口与ReentrantLock
配合使用,用于线程间的协调。线程在调用Condition
的await()
、signal()
等方法前,需要先获取相应的ReentrantLock
锁。
2. 灵活性与功能特性
wait()
和notify()
方法:使用wait()
方法时,线程会释放当前持有的对象监视器锁,进入等待状态,直到被其他线程唤醒。notify()
方法用于唤醒在该对象上等待的某个线程,notifyAll()
则会唤醒所有在该对象上等待的线程。ReentrantLock
和Condition
机制:ReentrantLock
提供了更灵活的锁机制,例如可响应中断、实现公平锁等。Condition
接口的await()
方法使线程等待,并释放ReentrantLock
锁,signal()
方法用于唤醒等待的线程。与wait()
和notify()
相比,Condition
可以创建多个条件对象,从而实现更精细的线程控制。
3. 可中断性
wait()
和notify()
方法:当线程处于wait()
状态时,如果被中断,会抛出InterruptedException
异常。ReentrantLock
和Condition
机制:ReentrantLock
提供了可中断的锁获取方式,如lockInterruptibly()
方法,使线程在等待锁的过程中能够响应中断。此外,Condition
的await()
方法也支持在等待过程中被中断。
4. 性能与可控性
wait()
和notify()
方法:由于是Java内置的同步机制,使用简单,但在复杂的线程协调场景中,可能显得不足。ReentrantLock
和Condition
机制:提供了更高的可控性和灵活性,适用于需要精细控制线程同步的场景,但相应地,代码复杂度也会增加。
问题:如果改成N个线程(N>2)交替打印呢
我们只需要额外添加一个变量,记录当前应该执行的线程ID即可。
实例代码:
java
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreeThreadPrint {
private static final int MAX_NUM = 100;
private int num = 1;
private int threadIdToRun = 1; // 当前应执行的线程ID
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public static void main(String[] args) {
ThreeThreadPrint printer = new ThreeThreadPrint();
Thread t1 = new Thread(printer.new Printer(1), "线程1");
Thread t2 = new Thread(printer.new Printer(2), "线程2");
Thread t3 = new Thread(printer.new Printer(3), "线程3");
t1.start();
t2.start();
t3.start();
}
private class Printer implements Runnable {
private int threadId;
public Printer(int threadId) {
this.threadId = threadId;
}
@Override
public void run() {
while (true) {
lock.lock();
try {
while (threadId != threadIdToRun) {
condition.await();
}
if (num > MAX_NUM) {
condition.signalAll();
break;
}
System.out.println(Thread.currentThread().getName() + ": " + num++);
threadIdToRun = threadId % 3 + 1;
condition.signalAll();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
}
}
}