线程之间的通信基本的三个方法:
- wait():一旦执行该方法,当前线程就会堵塞状态,并释放同步监视器(锁)
- notify():一旦执行该方法,就会唤醒被wait的一个线程,如果是多个,则唤醒优先级高的线程
- notifyAll():一旦执行该方法,就会唤醒所有被wait的线程
1.使用 synchronized 和 wait notify 来实现两个线程交替打印
public class TestPrintNumber {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number,"线程1");
Thread t2 = new Thread(number,"线程2");
t1.start();
t2.start();
}
}
class Number implements Runnable{
private int i = 0;
@Override
public void run() {
while (true){
synchronized (this) {
notify();
if(i < 100){
i++;
System.out.println(Thread.currentThread().getName()+"---"+i);
}else{
break;
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
线程1---1
线程2---2
线程1---3
线程2---4
线程1---5
线程2---6
..............
线程1---95
线程2---96
线程1---97
线程2---98
线程1---99
线程2---100
Process finished with exit code 0
这里需要注意:
- wait(),notify(),notifyAll()三个方法必须使用在同步代码块或者同步方法中
我们再将线程类改成如下:
```java
class Number implements Runnable{
private int i = 0;
//注意这里使用object对象作为锁
private Object object = new Object();
@Override
public void run() {
while (true){
synchronized (object) {
notify();
if(i < 100){
i++;
System.out.println(Thread.currentThread().getName()+"---"+i);
}else{
break;
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果会报错,报错信息如下:
我们还要注意wait(),notify(),notifyAll()三个方法的调用者必须是同步代码块或者同步方法中的同步监视器(锁),在上面的例子,同步代码块的锁为object对象,而现在notify和wait方法的调用者是this,即Number对象的实例,我们需要改成object去调用notify()和wait()方法即可。
补充:sleep()和wait()的异同
相同点:执行这两个方法,都可以使调用线程进入堵塞状态。
不同点:
1.声明位置不一样,Thread类中声明sleep(),Object类中声明的wait()方法。
2.调用要求不一样,sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或者同步方法中。
3.当两个方法都在同步代码块或者同步方法中,sleep()不会释放锁,wait()会释放锁。
2.使用Lock和Condition实现
在Condition对象中,与wait() ,notify() ,notifyAll()方法分别对应的是await() , signal() ,signalAll()方法。Condition实例实质上被绑定到一个锁上。要为特定Lock实例获得Condition实例,使用其newCondition()方法。
将第一个Number类改成以下代码,也可以实现一样的结果。
class Number implements Runnable {
private int i = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
@Override
public void run() {
while (true) {
//加锁
lock.lock();
try {
condition.signal();
if (i < 100) {
i++;
System.out.println(Thread.currentThread().getName() + "---" + i);
} else {
break;
}
condition.await();
} catch (Exception e) {
e.printStackTrace();
} finally {
//解锁
lock.unlock();
}
}
}
}
这里注意下lock()和unlock()方法调用的位置
阿里巴巴开发手册中写明:
在使用阻塞等待获取锁的方式中,必须在try代码块之外,并且在加锁方法与try代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在finally中无法解锁。
说明一:如果在lock方法与try代码块之间的方法调用抛出异常,那么无法解锁,造成其它线程无法成功获取锁。
说明二:如果lock方法在try代码块之内,可能由于其它方法抛出异常,导致在finally代码块中,unlock对未加锁的对象解锁,它会调用AQS的tryRelease方法(取决于具体实现类),抛出IllegalMonitorStateException异常。
说明三:在Lock对象的lock方法实现中可能抛出unchecked异常,产生的后果与说明二相同。 java.concurrent.LockShouldWithTryFinallyRule.rule.desc
Positive example:
Lock lock = new XxxLock();
// ...
lock.lock();
try {
doSomething();
doOthers();
} finally {
lock.unlock();
}
Negative example:
Lock lock = new XxxLock();
// ...
try {
// If an exception is thrown here, the finally block is executed directly
doSomething();
// The finally block executes regardless of whether the lock is successful or not
lock.lock();
doOthers();
} finally {
lock.unlock();
}