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;
}