在网上看到的额都说notify()的唤醒顺序是随机的,可是自己做的一个实验显示并不是如此:package adad;
public class MyThreadFactory {
// 线程A是否处于等待状态的标志
private boolean isThreadAWaiting;
// 线程B是否处于等待状态的标志
private boolean isThreadBWaiting;
// 线程C是否处于等待状态的标志
private boolean isThreadCWaiting;
public MyThreadFactory() {
isThreadAWaiting = true;
isThreadBWaiting = true;
isThreadCWaiting = true;
}
/**
* 对象锁
*/
private final Object object = new Object();
/**
* 该线程作为一个唤醒线程
*/
public void startWakenThread() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("唤醒线程开始执行...");
// 首先释放线程A
quitThreadA();
}
}
});
t.start();
}
/**
* 启动线程A
*/
public void startThreadA() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("线程A开始等待...");
try {
for (; ; ) {
if (!isThreadAWaiting) break;
object.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A结束...");
// 线程A结束后,暂停2秒释放线程B
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
quitThreadB();
}
}
});
t.start();
}
/**
* 启动线程B
*/
public void startThreadB() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("线程B开始等待...");
try {
for (; ; ) {
if (!isThreadBWaiting) break;
object.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程B结束...");
// 线程B结束后,暂停2秒释放线程C
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
quitThreadC();
}
}
});
t.start();
}
/**
* 启动线程C
*/
public void startThreadC() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println("线程C开始等待...");
try {
for (; ; ) {
if (!isThreadCWaiting) break;
object.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程C结束...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("所有线程执行完毕!");
}
}
});
t.start();
}
/**
* 线程A退出等待
*/
private void quitThreadA() {
isThreadAWaiting = false;
object.notify();
}
/**
* 线程B退出等待
*/
private void quitThreadB() {
isThreadBWaiting = false;
object.notify();
}
/**
* 线程C退出等待
*/
private void quitThreadC() {
isThreadCWaiting = false;
object.notify();
}
public static void main(String[] args) {
MyThreadFactory factory = new MyThreadFactory();
factory.startThreadB();//这儿的启动顺序注意
factory.startThreadA();
factory.startThreadC();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
factory.startWakenThread();
}
}
在网上找的这个例子,刚开始启动顺序是ABC,我改成BAC后运行程序多次都唤醒不了,这不正是说明唤醒跟其启动(启动的的时候会有个进入等待池也就是先进先出的顺序吗)
这个程序,貌似不能用作 线程唤醒无序的例子,每个线程里有两个变量来控制是否能运行,一个是object对象锁,一个是标记变量。
你的唤醒函数的设置标记的顺序是 ABC,但是object的notify不能保证按照ABC唤醒。因此你这个程序实际运行起来应该是有问题的
换句话说,即使你用object.notify(); 唤醒了B。 但是因为标记状态没有被A设置,因此唤醒不了3个线程。
何况你虽然是顺序start三个线程,但不代表这三个线程就一定是按你调用的顺序启动的。 三个线程太少,看不出随机性。
我自己写了一个测试随机唤醒的程序。 结果发现虽然结果不是有规律,但是也不是完全没有规律。 粗看下一个规律是。前面几个休眠的线程会按照顺序唤醒,后面的线程则会倒序唤醒。 具体还需要研究下。
import java.util.LinkedList;
import java.util.List;
public class ThreadRunSort {
/**
* 对象锁
*/
private final Object object = new Object();
private List<Integer> sleep = new LinkedList<>();
private List<Integer> notify = new LinkedList<>();
/**
* 该线程作为一个唤醒线程
*/
public void startThread(int i) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
try {
System.out.println(Thread.currentThread().getName()+"进入休眠");
sleep.add(i);
object.wait();
System.out.println(Thread.currentThread().getName()+"线程已经唤醒");
notify.add(i);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
});
t.setName("Thread"+i);
t.start();
}
public static void main(String[] args) {
ThreadRunSort a = new ThreadRunSort();
for(int i =1;i<22;i++){
a.startThread(i);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println();
for(int i =1;i<22;i++){
synchronized (a.object) {
a.object.notify();
}
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("休眠顺序"+a.sleep);
System.out.println("唤醒顺序"+a.notify);
}
}
看了楼上的回复,发现了一点问题。将他的例子中notify部分改动了一下。
for(int i =1;i<50;i++){
try {
Thread.sleep(10); // 在这里sleep确保notify会顺序执行
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a.object) {
a.object.notify();
}
}
最后执行结果:
休眠顺序[1, 4, 7, 6, 5, 2, 3, 10, 9, 13, 8, 12, 14, 11, 16, 15, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 25, 28, 30, 31, 29, 32, 34, 35, 36, 33, 38, 37, 39, 40, 41, 42, 43, 44, 45, 46, 48, 47, 49]
唤醒顺序[1, 4, 7, 6, 5, 2, 3, 10, 9, 13, 8, 12, 14, 11, 16, 15, 17, 18, 19, 20, 21, 22, 23, 24, 26, 27, 25, 28, 30, 31, 29, 32, 34, 35, 36, 33, 38, 37, 39, 40, 41, 42, 43, 44, 45, 46, 48, 47, 49]
结果显示唤醒顺序与执行顺序是一样的(注意这里是强调唤醒顺序,而不是重新获得锁的顺序)。
wait后的线程确实是保存在一个FIFO的等待队列中。
为什么楼上的结果唤醒顺序与休眠顺序不一致?
因为他打印的不是唤醒顺序,而是唤醒后线程重新获得锁的顺序。
在循环中直接notify的话,这些等待的线程虽然是按顺序唤醒,但是间隔时间非常短,几乎会同时去竞争锁(这一点和在循环中start线程却不能保证线程执行的顺序是一个道理),因此在竞争下是无法保证获取锁的顺序的。
总结:wait后的线程会进入一个FIFO的队列中,notify是一个有序的出队列的过程。而短时间内多个线程竞争获得锁的顺序则是不确定的,这要靠cpu调度决定。
但是如果把代码改成
synchronized (a.object) {
a.object.notifyAll();
}
顺序又变成
休眠顺序[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]
唤醒顺序[21, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
难道notify是一个有序的出队列的过程,notifyAll是出栈?