线程同步中有说用同步方法、同步代码块、lock锁实现线程同步,但有时候仅仅是同步还满足不了需求。例如,一个售票系统,分打印车票和售出车票两个线程,当无票时,要打印车票后才能售票,有票时直接售出。这样,两个线程之间就需要相互通信。
对于由synchronized修饰的同步方法和同步代码块,我们可以借助Thread类提供的wait()/notify()/notifyAll()三个方法来实现线程间通信。这三个方法必须由同步监视对象调用,这可以分为两种情况:
1.对于使用synchronized修饰的同步方法,同步对象就是this(默认实例),所以可以在同步方法内直接调用者三个方法。
2.对于使用synchronized修饰的同步代码块,同步对象是synchronized后括号里的对象,所以要使用这个对象调用这三个方法。
关于这三个方法的解释如下:
wait():导致当前线程等待,直到其他线程使用notify或notifyAll唤醒该线程。调用wait()方法的当前线程释放对同步监视器的锁定。wait()方法有三种形式:无参方法,要一直等待下去直到线程被唤醒;带有毫秒级或毫秒级、微秒级时间参数的wait(),线程等待指定的时间后自动苏醒。
notify():唤醒在此同步监视器上等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程,这种选择是任意性的。只有当前线程释放了同步监视器以后才可以唤醒其他线程。
notifyAll():唤醒在此同步监视器上等待的所有线程。
下面我们用wait和notify方法来实现售票程序的编程。代码如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Tickets {
private final Lock lock=new ReentrantLock();
private final Condition cond=lock.newCondition();
int size; //总票数
static int number=0; //票号
boolean available=false; //是否有票,一开始无票
public Tickets(int size){
this.size=size;
}
public static void main(String args[]){
Tickets t=new Tickets(3);
//System.out.println("111");
new Producer(t).start();
// new Consumer(t).start();
System.out.println("2222");
}
//印票
public void put(){
lock.lock();
try {
//如果还有存票则印票线程等待
if(available){
cond.await();
System.out.println("\n当前没有票!");
number++;
System.out.println("印刷第"+number+"号票");
cond.signal();
//唤醒售票线程
notify();
}
}
catch(Exception e){
e.printStackTrace();
}
finally{
lock.unlock();
}
}
//售票
public synchronized void sell(){
System.out.println("进入sell");
lock.lock();
//如果没有存票,则售票线程等待
try {
System.out.println("进入try");
if(!available) {
System.out.println("进入available");
cond.await();
System.out.println("\n当前有票...");
System.out.println("售出第"+number+"张票");
available=false;
cond.signal();
if(number==size) number=size+1;
}
}
catch(Exception e){
e.printStackTrace();
}
finally{
lock.unlock();
}
}
}
class Producer extends Thread{
Tickets t=null;
public Producer(Tickets t){
this.t=t;
}
public void run(){
while(t.number<t.size)
t.put();
}
}
class Consumer extends Thread{
Tickets t=null;
public Consumer(Tickets t){
this.t=t;
}
public void run(){
while(t.number<=t.size)
t.sell();
}
}
如果程序不使用synchronized关键字来保证同步不,而是直接使用lock锁来保证同步,系统版中不存在隐式的同步监视器对象,也就不能用wait、nontify、notifyAll来进行线程间通信。对于使用lock锁的线程,java使用Condition类来保证同步。Condition将同步监视器方法分成三个截然不同的对象,以便通过将这些对象与lock对象组合使用,来控制同步。在这种情况下,lock代替了同步代码块或同步方法,Condition代替了同步监视器。
Condition实例实质上被绑定在一个lock对象上。要获得特定的lock实例的Condition实例,调用lock的newCondition方法即可。Condition提供了三个方法:
await:相当于wait()
signal:相当于notify
signalAll:相当于notifyAll
同样是售票程序,我们用Condition来实现,其代码如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Tickets {
private final Lock lock=new ReentrantLock();
private final Condition cond=lock.newCondition();
int size; //总票数
static int number=0; //票号
boolean available=false; //是否有票,一开始无票
public Tickets(int size){
this.size=size;
}
public static void main(String args[]){
Tickets t=new Tickets(3);
new Producer(t).start();
new Consumer(t).start();
}
//印票
public void put(){
lock.lock();
try {
//如果还有存票则印票线程等待
if(!available){
System.out.println("\n当前没有票!");
number++;
System.out.println("印刷第"+number+"号票");
available=true;
//唤醒售票线程
cond.signal();
}else{
cond.await();
}
}
catch(Exception e){
e.printStackTrace();
}
finally{
lock.unlock();
}
}
//售票
public synchronized void sell(){
lock.lock();
//如果没有存票,则售票线程等待
try {
if(available) {
System.out.println("\n当前有票...");
System.out.println("售出第"+number+"张票");
available=false;
cond.signal();
if(number==size) number=size+1;
}else{
cond.await();
}
}
catch(Exception e){
e.printStackTrace();
}
finally{
lock.unlock();
}
}
}
class Producer extends Thread{
Tickets t=null;
public Producer(Tickets t){
this.t=t;
}
public void run(){
while(t.number<t.size)
t.put();
}
}
class Consumer extends Thread{
Tickets t=null;
public Consumer(Tickets t){
this.t=t;
}
public void run(){
while(t.number<=t.size)
t.sell();
}
}
java中还有一种实现同步的机制:使用管道流。不过,这种方式多用在进程间通信,线程之间很少使用。等以后用到进程间通信再学习吧。