三个线程循环打印ABC多次,要求同时开启多个线程
核心思路:三个线程分别打印A,B,C;一个执行完后唤醒另外一个线程。主要使用Object.wait()
和Object.notify()
这两个方法。
实现方法
- A->B, B->C,C->A之间状态的转换分别使用不同的对象锁
- 不同状态之间的转换使用同一个状态锁,完成后使用notifyAll,并在自己的线程中检查,是否满足执行条件,不满足则继续wait。
代码实现
- 使用不同的对象锁
这种方法其实是有问题的,由于嵌套的同步对象,存在死锁的风险,在两个的时候就比较容易提现出来。3个线程就不太容易复现,因为要3个线程同时进入Thread.yeild()
那段代码,然后同时唤醒,都想要获取next的监视器锁,但是都获取不到。
public class CrycleThreader {
private void test() {
LockHolder A = new LockHolder();
LockHolder B = new LockHolder();
LockHolder C = new LockHolder();
A.setReady(true);
CycleClass cycleA = new CycleClass(A, B, "A", 10);
CycleClass cycleB = new CycleClass(B, A, "B", 10);
// CycleClass cycleC = new CycleClass(C, A, "C", 10);
cycleA.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
cycleB.start();
// cycleC.start();
}
public static void main(String[] args) {
new CrycleThreader().test();
// new CrycleThreader().testFail();
}
//
private class CycleClass extends Thread {
private volatile LockHolder done;
private volatile LockHolder next;
private String name;
private int count;
/**
*
*/
public CycleClass(LockHolder done, LockHolder next, String name, int count) {
this.done = done;
this.next = next;
this.name = name;
this.count = count;
}
@Override
public void run() {
while (count-- > 0) {
synchronized (done) {
while (!done.isReady()) {
try {
done.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
done.setReady(false);
next.setReady(true);
System.out.println(name + "--> " + count);//如果放到next.notify()后面 会不正常的
//Thread.yield();
synchronized (next) {
next.notify();
}
}
}
}
}
private class LockHolder {
private boolean ready = false;
/**
* Getter method for property <tt>ready</tt>.
*
* @return property value of ready
*/
public boolean isReady() {
return ready;
}
/**
* Setter method for property <tt>ready</tt>.
*
* @param ready
* value to be assigned to property ready
*/
public void setReady(boolean ready) {
this.ready = ready;
}
}
}
- 使用同一个监视器锁,
一个线程结束后唤醒所有的线程,让其自己判断是否要往下执行。封装了锁持有器,所有的线程都对锁同步,在这个对象中显示出接下来该执行哪个。只有一个锁,不会有死锁的情况,但是每次都是notifyAll,在多线程的情况下可能会造成线程切换上下文换入换出。 但是在我的机子上3个线程2比1块
public class CycleHandler {
// time cost 3722
public void doTest() {
String A = "A";
String B = "B";
String C = "C";
int count = 100000;
LockHolder holder = new LockHolder();
holder.setReadyObj(A);
CountDownLatch latch = new CountDownLatch(1);
Cycle cycleA = new Cycle(A, B, holder, count, latch);
Cycle cycleB = new Cycle(B, C, holder, count, latch);
Cycle cycleC = new Cycle(C, A, holder, count, latch);
long startTime = System.currentTimeMillis();
cycleA.start();
cycleB.start();
cycleC.start();
latch.countDown();
try {
cycleA.join();
cycleB.join();
cycleC.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
System.out.println("execute time: " + (endTime - startTime));
}
public static void main(String[] args) {
new CycleHandler().doTest();
}
private class Cycle extends Thread {
private final LockHolder lockHoder;
private Object next;
private Object cur;
private int count;
private CountDownLatch latch;
public Cycle(Object cur, Object next, LockHolder holder, int count, CountDownLatch lacth) {
this.cur = cur;
this.next = next;
this.lockHoder = holder;
this.count = count;
this.latch = lacth;
}
@Override
public void run() {
try {
latch.await();
} catch (InterruptedException e1) {
// logger.error("", e);
e1.printStackTrace();
}
while (count-- > 0) {
synchronized (lockHoder) {
while (!lockHoder.getReadyObj().equals(cur)) {
try {
lockHoder.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(cur.toString() + "--> " + count);
lockHoder.setReadyObj(next);
lockHoder.notifyAll();
}
}
}
}
private class LockHolder {
private Object readyObj;
public Object getReadyObj() {
return readyObj;
}
public void setReadyObj(Object readyObj) {
this.readyObj = readyObj;
}
}
}
其他
- 在使用wait和notify这两个方法必须在该对象的同步区内。否则会抛出异常
IllegalMonitorStateException
这两个是native的方法,看不到细节 - 不要使用嵌套的对象锁,可能会出现死锁。
- 内部类的对象可以使用外部类的属性(Outter.this.xxx)
- 监视器锁不能被赋值,一旦改了就不是同一个监视器锁了
- 采用自旋锁避免线程的异常唤醒(检查下是否满足往下执行的条件)