题目
评测题目: 开启 3 个线程 1,2,3,这三个线程的输出分别为 1、2、3,每个线程将自己的 输出在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。如:123123...
分析
多个线程间顺序执行,如何实现多线程同步,主要就是借助各种锁来控制。
答案
定义一个基类:
public static class BTH extends Thread {
static long start;
static long end;
}
解法1 对象锁+运行条件标记
static class MyThread extends BTH {
static volatile int Flag = 1;
static Object lock = new Object();
private final int name;
private final int nextFlag;
MyThread(int name, int nextFlag) {
this.name = name;
this.nextFlag = nextFlag;
}
@Override
public void run() {
if (name == 1) {
start = System.currentTimeMillis();
}
for (int i=0; i< 1000; i++) {
for (;;) {
synchronized (lock) {
if (Flag == name) {
System.out.print(name);
Flag = nextFlag;
lock.notifyAll(); //唤醒等待的线程
break;
} else {
try {
lock.wait(); //让线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread.yield();
}
if (name == 3) {
end = System.currentTimeMillis();
}
}
}
@Test
public void test() throws InterruptedException {
Thread t1 = new MyThread(1, 2);
Thread t2 = new MyThread(2, 3);
Thread t3 = new MyThread(3, 1);
t1.start();
t2.start();
t3.start();
for(;;) {
Thread.sleep(5000);
break;
}
}
结果:循环1000次 耗时60ms
这种做法存在一个线程多次抢到锁的case,效率不高.
解法2 借助线程安全的同步计数
static class MyThread1 extends BTH {
static AtomicInteger sFlag = new AtomicInteger(1);
final int me;
final int next;
MyThread1(int who, int next) {
me = who;
this.next = next;
}
@Override
public void run() {
if (me == 1) {
start = System.currentTimeMillis();
}
for (int i=0; i< 1000; i++) {
for(;;) {
if (sFlag.get() == me) {
System.out.print(me);
sFlag.compareAndSet(me, next);
break;
}
Thread.yield();
}
}
if (me == 3) {
end = System.currentTimeMillis();
}
}
}
@Test
public void test() throws InterruptedException {
Thread t1 = new MyThread1(1, 2);
Thread t2 = new MyThread1(2, 3);
Thread t3 = new MyThread1(3, 1);
t1.start();
t2.start();
t3.start();
for(;;) {
Thread.sleep(5000);
break;
}
}
结果:循环1000次,耗时24ms
借助线程安全的AntomicInteger来作为线程运行的标记,简单的实现了线程顺序执行。
解法3 使用信号量
static class Thread2 extends BTH {
private final Semaphore mSemaphore;
private final Semaphore mNext;
private final int me;
public Thread2(Semaphore my, Semaphore next, int me) {
this.mSemaphore = my;
this.mNext = next;
this.me = me;
}
@Override
public void run() {
if (me == 1) {
start = System.currentTimeMillis();
}
for (int i=0; i < 1000; i++) {
try {
mSemaphore.acquire();
System.out.print(me);
mNext.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (me == 3) {
end = System.currentTimeMillis();
}
}
}
@Test
public void test() throws InterruptedException {
Thread t1 ;
Thread t2 ;
Thread t3 ;
{
//case3
Semaphore s1 = new Semaphore(1);
Semaphore s2 = new Semaphore(0);
Semaphore s3 = new Semaphore(0);
t1 = new Thread2(s1, s2, 1);
t2 = new Thread2(s2, s3, 2);
t3 = new Thread2(s3, s1, 3);
}
t1.start();
t2.start();
t3.start();
for(;;) {
Thread.sleep(5000);
break;
}
System.out.println("\n耗时" + (BTH.end - BTH.start));
}
结果:循环1000次,耗时79ms
解法4 使用可重置锁
static class Thread3 extends BTH {
static int sFlag = 1;
private static ReentrantLock lock = new ReentrantLock();
private final Condition mCondition;
private final Condition nextCondition;
private final int me;
private final int next;
public Thread3(int me, int next, Condition condition, Condition nextCondition) {
this.mCondition = condition;
this.nextCondition = nextCondition;
this.me = me;
this.next = next;
}
@Override
public void run() {
if (me == 1) {
start = System.currentTimeMillis();
}
try {
lock.lock();
for (int i=0; i<1000; i++) {
while ( sFlag != me) {
mCondition.await(); //刮起当前线程
}
System.out.print(me);
sFlag = next;
nextCondition.signal(); //唤醒下一个线程
}
} catch (InterruptedException e) {
} finally {
lock.unlock();
}
if (me == 3) {
end = System.currentTimeMillis();
}
}
}
@Test
public void test() throws InterruptedException {
Thread t1 ;
Thread t2 ;
Thread t3 ;
{
Condition c1 = Thread3.lock.newCondition();
Condition c2 = Thread3.lock.newCondition();
Condition c3 = Thread3.lock.newCondition();
t1 = new Thread3(1, 2, c1, c2);
t2 = new Thread3(2, 3, c2, c3);
t3 = new Thread3(3, 1, c3, c1);
}
t1.start();
t2.start();
t3.start();
for(;;) {
Thread.sleep(5000);
break;
}
System.out.println("\n耗时" + (BTH.end - BTH.start));
}
结果:循环1000次,耗时36ms