下面是“代码分析Java中线程的等待与唤醒”的完整攻略:
1. 什么是线程等待和唤醒
在Java中,线程等待和唤醒是多线程编程中重要的概念之一。线程等待和唤醒通常发生在一个共享对象上,例如一个锁或者是一个共享的变量。简单来说,线程等待和唤醒的作用是让线程在满足某些条件之前暂停或者执行某段代码之前等待某些条件达成。
具体而言,线程等待通常与线程同步机制(如synchronized关键字或者lock)一起使用,用于暂停线程,直到某些条件得到满足。线程等待会将当前线程放入等待队列中,这里需要注意,线程只能等待持有相同对象的其他线程,也就是说在某个共享对象上只能存在一个等待队列。
相应的,线程唤醒则是让等待队列中的一个线程从等待状态变为就绪状态,因此线程唤醒实际上就是通知正在等待的线程可以继续执行,一般与notify和notifyAll方法相关联,通常也需要在同步块中使用。
2. 线程等待和唤醒的实现方式
Java中实现线程等待和唤醒的方式通常有两种,wait()和notify()方法和Lock和Condition接口。
2.1 wait()和notify()方法
wait()和notify()方法是Java中实现程序的线程等待和唤醒的较为常用的方法。 一个线程可以通过wait()方法进入等待状态,使其它线程能够获得锁并执行一些任务。而另一个线程通过notify()方法来通知某个线程已经完成了特定任务,以便让该线程离开等待状态。
wait()方法的语法如下:
public final native void wait(long timeout) throws InterruptedException;
public final void wait() throws InterruptedException;
public final native void notify();
public final native void notifyAll();
wait()方法会将调用线程挂起,直到另外的线程调用notify()或notifyAll()方法。wait()方法会在Object对象的监视器中等待,并且只能被在同步块中(即synchronized)调用。同时,wait()方法还支持提供超时时间,即到达指定的超时时间后wait方法会自动返回,并且线程状态变为就绪态。
notify()方法则是唤醒等待中的一个线程。如果有多个线程在等待,那么唤醒其中任意一个线程即可,具体唤醒哪一个线程由JVM底层决定。notifyAll()方法则是唤醒所有在等待中的线程,使得它们都进入就绪状态,等待被再次调度。
下面给出一段代码示例,通过wait和notify来实现一次精准唤醒,即只唤醒指定的某一个线程:
public class WaitAndNotifyDemo implements Runnable {
final int MAX_LIMIT = 3;
int current = 0;
public static void main(String[] args) {
WaitAndNotifyDemo wad = new WaitAndNotifyDemo();
for (int i = 0; i < wad.MAX_LIMIT; i++) {
new Thread(wad).start();
}
}
@Override
public void run() {
synchronized (this) {
// 当前线程的序号
int num = current++;
// 等待指定序号的线程
while (current % MAX_LIMIT != num) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 输出当前线程的序号
System.out.println("Thread-" + num);
// 唤醒下一个线程
notifyAll();
}
}
}
上述代码中,我们设定了一个最大次数为3的计数器,当计数器的数值不等于当前线程的编号时,我们进入等待,否则输出当前线程编号,并唤醒下一条线程。
2.2 Lock和Condition接口
Java5之后,JDK中又增加了Lock和Condition接口,相比wait和notify方式,Lock和Condition方式用起来更自由,同时能够更方便地解决那些很容易出现的死锁问题。
Lock接口的使用需要进行显示加锁和释放锁,Condition提供了await()等待和signal()唤醒方法。下面是一个使用Lock和Condition的示例代码:
public class LockAndConditionDemo {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private volatile boolean isPing = true;
public static void main(String[] args) {
LockAndConditionDemo lockAndConditionDemo = new LockAndConditionDemo();
//线程A
Thread threadA = new Thread(() -> {
for (int i = 0; i < 10; i++) {
lockAndConditionDemo.printPing(i);
}
});
//线程B
Thread threadB = new Thread(() -> {
for (int i = 0; i < 10; i++) {
lockAndConditionDemo.printPong(i);
}
});
threadA.start();
threadB.start();
}
public void printPing(int i) {
lock.lock();
try {
while (!isPing) {
condition.await();
}
System.out.println("ping:(" + i + ")");
isPing = false;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printPong(int i) {
lock.lock();
try {
while (isPing) {
condition.await();
}
System.out.println("pong:{" + i + "}");
isPing = true;
condition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
输出:
ping:(0)
pong:{0}
ping:(1)
pong:{1}
ping:(2)
.....
我们使用了一个Lock对象和两个Condition对象实现了线程的等待和唤醒,不同的线程会在不同的Condition对象上等待,并且使用volatile型变量isPing来控制打印Pong和Ping。从输出结果中我们可以看到Ping和Pong会依次打印,符合预期。
综上所述,以上两种方法都是实现Java多线程中的线程等待和唤醒的方式。在使用的时候根据实际情况判断使用哪一种方法,需要注意的是,线程等待和唤醒最好放在同一个同步块或方法中,否则会存在线程不安全的情况。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:代码分析Java中线程的等待与唤醒 - Python技术站