除了使用synchronized来对代码块和方法进行同步外,jdk1.5之后还有一种Lock同步锁的方式进行同步:使用lock.lock()来进行加锁,使用lock.unlock()方法来释放锁,既然可以使用lock来代替synchronized,那么如何进行处理synchronized与wait()、notify()、notifyAll()的线程通信机制呢,在使用lock时使用Condition来进行线程通信。
下面来使用两种方式来分别展示一下线程间通信。
使用Object的wait()、notify进行线程通信
阻塞
调用wait()方法,将阻塞挂起等待其他线程的通知,进入该对象的线程等待池中,调用wait()方法之前,线程必须要获得该对象的对象级别锁,即只能在同步块或者同步方法中使用,否则会报IllegalMonitorStateException异常。
唤醒
调用notify()方法和notifyAll()方法来通知那些等待该对象锁的其他线程,(如果有多个线程等待,notify会任意挑选一个线程来进行通知,notifyAll会对所有该对象上由于调用wait方法而被挂起的线程进行通知,),唤醒之后的线程会进入该对象的锁池中,需要竞争到对象的监视器锁才可以继续执行,调用notify()方法和notifyAll()方法之前,线程必须要获得该对象的对象级别锁,即只能在同步块或者同步方法中使用。
除了调用notify()方法和notifyAll()方法来唤醒线程之外,还可以使用中断的方式来使得线程在wait()处抛出InterruptedException异常而返回并终止。
如果使用的是wait(timeout)方法进行阻塞的,那么也会由于在指定时间内没有被唤醒而因为超时返回。
/*** wait()/notify()/notifyAll()是Object的方法* 只能在synchronized方法或者代码块中使用,否则会报IllegalMonitorStateException异常* 交替打印1-100*/
public class TestThreadSignal {public static void main(String[] args) {PrintRunnable runnable = new PrintRunnable();new Thread(runnable,"线程一").start();new Thread(runnable,"线程二").start();}
}
class PrintRunnable implements Runnable{int num = 1;@Overridepublic void run() {while (true){synchronized (this){notify();// 唤醒wait的线程if(num <= 100){System.out.println(Thread.currentThread().getName() + "打印" + num++);} else {break;}try {wait(); // 释放当前的锁} catch (InterruptedException e) {e.printStackTrace();}}}}
}
虚假唤醒现象
有时在没有被其他线程调用notify/notifyAll,或者没有被中断,或者没有等待超时,也有可能会从挂起状态变为运行状态(虚假唤醒),虽然概率很低,但是最好还是使用while(条件)来一直查看条件是否满足。
synchronized(obj){while(条件){obj.wait();}
}
使用condition的await()、signal()进行线程通信
/*** 使用lock和condition来进行线程通信* condition中的await()、signal()、signalAll()方法分别对应于Object中的wait()、notify()、notifyAll()方法* 两个线程交替打印1-100*/
public class TestThreadSignalForLock {public static void main(String[] args) {PrintRunnableForLock runnable = new PrintRunnableForLock();new Thread(runnable,"线程一").start();new Thread(runnable,"线程二").start();}
}
class PrintRunnableForLock implements Runnable{int num = 1;Lock lock = new ReentrantLock();Condition condition = lock.newCondition();@Overridepublic void run() {while (true){try{lock.lock();condition.signal();// 唤醒线程if(num <= 100){System.out.println(Thread.currentThread().getName() + "打印" + num++);} else {break;}try {condition.await(); // 释放当前的锁} catch (InterruptedException e) {e.printStackTrace();}} finally {lock.unlock();}}}
}
TestThreadSignalForLock
类中的 main
方法创建了一个 PrintRunnableForLock
对象的实例,并用这个实例创建了两个线程(线程一和线程二)。PrintRunnableForLock
类实现了 Runnable
接口,并重写了 run
方法。这个类有一个 num
变量用于记录当前要打印的数字,一个 Lock
对象用于同步,以及一个 Condition
对象用于线程间的通信。 在run
方法中,每个线程首先获取锁,然后调用 condition.signal()
唤醒其他等待的线程。接着,如果 num
小于等于100,就打印当前的数字,并将 num
加1。之后,线程会尝试调用 condition.await()
进入等待状态,并释放锁。