主动释放锁,是通过什么方法来达到的呢?
如果,我们看过Object类的方法,我们会注意到它有三个奇怪的方法:wait(),notify()和notifyAll()。 它们就是用来释放锁的。
1 线程的状态#
在讲锁的释放前,我们先讲一下线程的三种主要状态:运行、就绪(可运行)、阻塞。当然,除了这个之外,肯定还有初始状态和结束状态,那个就不讨论了。
threestates.png
当我们创建线程之后,还只是进入初始状态,如果我们调用run()方法运行,根本就不会启动新的线程。当调用start()后,可以将线程状态改为可运行状态,然后,由操作系统来决定调用哪个线程。当幸运的被操作系统选中之后,就可以进入真正的运行状态了。当运行当时间片用完,或者调用yield()礼让方法,就会把当前当执行权交出来,进入可运行就绪状态。如果在运行的过程中,有系统IO,如等待输入,弹出确认对话框等,则会使当前线程进入阻塞状态,动弹不得。只有等待用户操作之后,才能往下进行。sleep()方法和join()方法可以可以达到类似的阻塞效果。
morestates.png
然后,我们把对象锁部分的状态也加进来。当我们使用synchronized修饰方法或者代码块时,会获取对象的锁,并进入获取该对象锁的等待池,由操作系统来决定调用哪个线程(非公平锁)。当获取到该对象锁之后,就可以进入可运行状态了。
另外,还有一个对象的wait()方法,可以使线程放弃持有的该对象锁,并进入通知等待状态。当其他线程调用等待线程需要的对象的notify()或者notifyAll()方法时,该线程重新进入获取对象锁的队列中参与锁的获取。
2 wait() notify() 和 notifyAll()#
synchronized获取锁的方法我们已经详细的讲解过了,我们接下来来看一下如何主动释放锁。假设,我们想创建两个线程,让这两个线程,依次做一件事情,而不是同时启动之后,就由操作系统来决定它们的执行顺序,那么,我们该怎么做呢?
那就是首先请求前一个线程的锁,然后,获取自己线程的锁,再释放自己的锁,并通知这个锁对象的等待队列(下一个线程!哎哎,醒醒别睡啦,起来干活啦!),释放前一个线程的锁,并进入等待前一个锁对象的通知队列。
twothreads.png
用代码展示为:
public class SyncDemo implements Runnable {
private String name;
private Object prev;
private Object cur;
public SyncDemo(String name, Object prev, Object cur) {
this.name = name;
this.prev = prev;
this.cur = cur;
}
@Override
public void run() {
int count = 10;
while (count > 0) {
synchronized(prev) {
synchronized(cur) {
System.out.println(name);
count--;
cur.notify();
}
try {
prev.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Object a = new Object();
Object b = new Object();
SyncDemo r1 = new SyncDemo("A", b, a);
SyncDemo r2 = new SyncDemo("B", a, b);
new Thread(r1).start();
try {
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
new Thread(r2).start();
}
}
//代码效果参考:http://www.zidongmutanji.com/zsjx/31939.html
写得更通用一点,支持更多的线程依次执行:
public static void main(String[] args) {
int num = 5;
Object[] locks = new Object[num];
String[] names = {"A", "B", "C", "D", "E"};
SyncDemo[] runnable = new SyncDemo[num];
for (int i = 0; i < num; i++) {
locks[i] = new Object();
}
for (int i = 0; i < num; i++) {
runnable[i] = new SyncDemo(names[i], locks[(i - 1 + num) % num] , locks[i]);
}
for (int i = 0; i < num; i++) {
new Thread(runnable[i]).start();
try {
Thread.sleep(100);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}