线程通信简单实例

1.Object类的wait()、notify()和notifyAll(),必需由同步监视器对象来调用

  • wait(),导致当前线程等待,直到其他线程调用该同步监视器的notify()或notifyAll()来唤醒该线程,调用该方法的当前线程会释放对该同步监视器的锁定
  • notify(),唤醒在此同步监视器上等待的单个线程,如果所有线程都在该同步监视器上等待,则会选择唤醒其中一个,选择是任意性的,只有当前线程放弃对该同步监视器的锁定(使用wait方法)后,才可以执行被唤醒的线程
  • notifyAll(),唤醒在此同步监视器上等待的所有线程

eg:建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC

package jichu;

/*
建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C
要求线程同时运行,交替打印10次ABC
思路:Thread A->Thread B->Thread C->Thread A循环
为了控制顺序,必须确定唤醒、等待的顺序,每个线程必须同时有两个对象锁,prev前一个;self自身
先持有prev,也就是前一个线程要释放自身对象锁,再去申请自身对象锁,两者兼备打印
之后先调用self.notify释放自身,唤醒下一个等待线程,再调用prev.wait释放prev对象锁,终止当前线程,等待循环结束后再次被唤醒
A先运行,持有CA,后释放AC,唤醒B;B等待A,再申请B,后打印B,再释放BA,唤醒C;C等待B,再申请C,打印后释放CB,唤醒A
 */
public class MyThreadPrinter implements Runnable {
    private String name;
    private Object prev;
    private Object self;

    private MyThreadPrinter(String name,Object prev,Object self){
        this.name=name;
        this.prev=prev;
        this.self=self;
    }

    public void run(){
        int count=10;
        while(count>0){
            synchronized (prev){
                synchronized (self){
                    System.out.print(name);
                    count--;
//                    调用self.notify()释放自身对象锁,唤醒下一个等待线程
                    self.notify();
                }
                try{
//                	调用prev.wait()释放prev锁,终止当前等待线程,等待循环结束后再次被唤醒
                    prev.wait();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main (String [] args)throws Exception{
        Object a=new Object();
        Object b=new Object();
        Object c=new Object();
        MyThreadPrinter pa=new MyThreadPrinter("A",c,a);
        MyThreadPrinter pb=new MyThreadPrinter("B",a,b);
        MyThreadPrinter pc=new MyThreadPrinter("C",b,c);

        new Thread(pa).start();
        Thread.sleep(100);
        new Thread(pb).start();
        Thread.sleep(100);
        new Thread(pc).start();
        Thread.sleep(100);
    }
}

2. Condition

如果程序不使用synchronized关键字来保证同步,而是直接用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就不能使用上述方法

当使用Lock对象来保证同步,java提供一个Condition类来保持协调,使用Condition可以让那些已经得到Lock对象而无法继续执行的线程释放Lock对象,Condition对象也可以唤醒其他处于等待的线程

eg:和Object类的wait()、notify()和notifyAll()一样,当线程使用condition.await()时,要求线程持有相关的重入锁,在condition.await()调用后,这个线程会释放这把锁,signal同理,在signal方法被调用后,系统会从当前Condition对象的等待队列中唤醒一个线程,一旦线程被唤醒,它会重新尝试获得与之绑定的重入锁,一旦成功获取即可执行

package LockTest;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class LockCondition implements Runnable {
	public static ReentrantLock lock=new ReentrantLock();
//	通过lock生成一个与之绑定的condition对象
	public static Condition condition=lock.newCondition();
	public void run(){
		try{
			lock.lock();
			condition.await();//线程在condition对象上进行等待
			System.out.println("Thread is going on");
		}catch(InterruptedException e){
			e.printStackTrace();
		}finally{
			lock.unlock();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		// TODO Auto-generated method stub
		LockCondition lc=new LockCondition();
		Thread t1=new Thread(lc);
		t1.start();
		Thread.sleep(2000);
//		主线程main发出通知,告诉等待在condition上的线程可以继续执行了
		lock.lock();
		condition.signal();
		lock.unlock();
	}

}

这种方式在JDK内部被广泛使用,eg,ArrayBlockingQueue

ArrayBlockingQueue的内部元素都放置在一个对象数组中:

    /** The queued items */
    final Object[] items;
  • offer(),如果当前队列已经满了,立即返回false
  • put(),如果队列满了,则会一直等待,直到队列中有空闲位置
  • poll(),如果队列为空,直接返回null
  • take(),如果队列空,一直等待,直到队列中有可用元素

为了做好等待和通知,ArrayBlockingQueue定义如下字段:

    /** Main lock guarding all access */
    final ReentrantLock lock;

    /** Condition for waiting takes */
    private final Condition notEmpty;

    /** Condition for waiting puts */
    private final Condition notFull;

当执行take操作,如果队列为空,则让当前线程等待在notEmpty上:

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

新元素入队列时,则进行一次notEmpty上的通知:

    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        notEmpty.signal();
    }

同理,对于put,当队列满时,让压入线程等待:

    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }

当有元素从队列中被挪走,队列中出现空位,通知等待入队的线程:

    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        notFull.signal();
        return x;
    }

猜你喜欢

转载自blog.csdn.net/Autumn03/article/details/81094281