多线程编程中,如果每个线程之间互相独立,那么将会使多线程带来的优势不能够很好地发挥出来。使用线程间通信,可以使得原先的互相独立的多个线程之间,能够很好地互相协作,使得系统之间的交互性得到提升,大大提高了CPU利用率,从而完成一些复杂的多线程功能模块。
多线程间的通信一般采取等待/通知机制进行实现。见名知意,等待通知就是处于等待状态的线程需要由其他线程发出通知,从而可以再次获得CPU资源,执行之前没有执行完的任务。
Java中,常用的实现等待/通知机制的两种方式是:
一、在使用synchronized关键字实现的同步方法或同步代码块中,由被上锁的对象调用其自身的wait()方法和notify()方法以及notifyAll()方法,进行实现等待/通知机制;
二、在使用ReentrantLock类的对象实现的同步方法或同步代码块中,使用Contion类的对象的await()方法和signal()方法以及signalAll()方法,进行实现等待/通知机制;
在实现等待/通知机制之前,要注意以下事项:
一、wait()方法和notify()方法以及notifyAll()方法的使用必须在同步方法或者同步代码块中,因为这三个方法需要获得监视器对象的对象级别的锁;同样地,await()方法和signal()方法以及signalAll()方法的使用必须在其所属的Contion类的对象所关联的ReentrantLock对象(锁对象)所作用的方法或者代码块中;
二、wait()方法和await()方法的调用有两个作用:1、使当前线程由运行状态变为等待状态;2、释放当前线程持有的锁;
三、notify()方法和signal()方法的调用只有一个作用:唤醒因为wait()或await()而处于等待状态的线程;
synchronized同步中的等待/通知机制实现,示例代码:
package com.WaitAndNotify;
public class CommunicationInSynchronized {
public static void main(String[] args) {
String lock = "";//作为synchronized的对象监视器
CommunicationInSynchronized cis = new CommunicationInSynchronized ();//产生实例,用于创建内部类实例
Thread threadW = new Thread(cis.new ThreadW(lock));//实例化等待线程
threadW.setName("W");//给等待线程命名
threadW.start();//启动等待线程
Thread threadN = cis.new ThreadN(lock);//实例化通知线程
threadN.setName("N");//给通知线程命名
threadN.start();//启动通知线程
}
//实现Runnable接口的等待线程类
class ThreadW implements Runnable{
private String lock;
public ThreadW(String lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized(lock) {
try {
System.out.println(
Thread.currentThread().getName() +
" begin wait time:" + System.currentTimeMillis());
lock.wait();//当前线程进入等待队列,同时释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( Thread.currentThread().getName()
+" end wait time:" + System.currentTimeMillis());
}
}
}
//通知线程类
class ThreadN extends Thread{
private String lock;
public ThreadN(String lock) {
this.lock = lock;
}
@Override
public void run() {
synchronized(lock) {
try {
System.out.println(
Thread.currentThread().getName() +
" begin notify time:" + System.currentTimeMillis());
lock.notify();//通知等待队列中的线程,使其进入就绪队列,等待获取锁
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +
" end notify time:" + System.currentTimeMillis());
}
}
}
}
运行结果如下图所示:
ReentrantLock锁同步中的等待/通知机制实现,示例代码2:
package com.waitAndNotify;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class CommunicationInReentrantLock {
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();//同步锁对象
Condition condition = lock.newCondition();//对应于同步锁对象的用于执行等待和通知命令的对象
CommunicationInReentrantLock cis = new CommunicationInReentrantLock ();//实例化对象,用于创建内部类的实例
Thread threadW = new Thread(cis.new ThreadW(lock,condition));//实例化等待线程
threadW.setName("W");//命名等待线程
threadW.start();//启动等待线程
Thread threadN = cis.new ThreadN(lock,condition);//实例化通知线程
threadN.setName("N");//命名通知线程
threadN.start();//启动通知线程
}
//实现Runnable接口的等待线程类
class ThreadW implements Runnable{
private Lock lock;
private Condition condition;
public ThreadW(ReentrantLock lock,Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
lock.lock();//上锁
try {
System.out.println(Thread.currentThread().getName()
+ " begin wait time:" + System.currentTimeMillis());
condition.await();//使当前线程进入等待队列,同时释放当前持有的锁
System.out.println(Thread.currentThread().getName()
+ " end wait time:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();//释放锁
}
}
}
//通知线程类
class ThreadN extends Thread{
private Lock lock;
private Condition condition;
public ThreadN(ReentrantLock lock,Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
lock.lock();//上锁
try {
System.out.println(Thread.currentThread().getName() +
" begin notify time:" + System.currentTimeMillis());
condition.signal();//通知等待队列中的线程,使其进入就绪队列,准备获取锁
Thread.sleep(3000);
System.out.println(
Thread.currentThread().getName() +
" end notify time:" + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();//释放当前持有的锁
}
}
}
}
运行结果如下图所示:
上述两个示例,实现了最基本的等待/通知机制,从两个示例的运行结果可以看出,两种方式实现结果完全一致,所以这两种方法都可以用于实现线程间的通信;但是线程间的通信较为复杂,实际应用中需要注意很多比较容易踩的”坑”;因此,需要多积累,多实践。
通常开发过程中,等待/通知机制的经典案例就是生产者/消费者模式,生产者消费模式又分为单生产者单消费者模式、单生产者多消费者模式、多生产者单消费者模式以及多生产者多消费者模式;每一种模式中需要注意的点不同,可能会遇到线程的”假死”和因为等待条件的改变而导致的异常;这一部分将会在另一篇博文进行阐述!