如何控制线程的执行顺序,这在面试中也会出的,列列些例子。

先打印2,再打印1

线程t1只打印1,线程t2只打印2,怎么做才能保证先让t2线程打印呢?

使用wait()和notify()

使用变量判断t2是否打印了,然后用经典的while(cond)判断+wait()

static final Object lock = new Object();
// 表示t2是否运行过
static boolean t2runned = false;
/**
 * 先打印2后打印1
 */
private static void order1() {
    Thread t1 = new Thread(() -> {
        synchronized (lock){
            // 防止虚假唤醒
            while (!t2runned){
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        log.debug("1");
    }, "t1");

    Thread t2 = new Thread(() -> {
        synchronized (lock){
            // 等t2打印完,再通知t1打印
            log.debug("2");
            t2runned = true;
            lock.notifyAll();
        }

    }, "t2");

    t1.start();
    t2.start();
}

park()和unpark()

非常容易。。。

Thread t1 = new Thread(() -> {
    LockSupport.park();
    log.debug("1");
}, "t1");
t1.start();

new Thread(() -> {
    log.debug("2");
    LockSupport.unpark(t1);
}, "t2").start();

abc交替输出

线程1输出a5次,线程2输出b5次,线程3输出c5次

wait, nofity

使用一个变量标记,标记当前线程的值是否是能够打印的,如果是,就打印,然后转到下一个;如果不是,就让当前线程wait。

我们可以预设,t1线程要打印的字符是a,标记变量为1,下一标记为2;

t2线程要打印的字符是b,标记变量为2,下一标记为3;

t3线程要打印的字符是c,标记变量为3,下一标记为1

只要不满足条件,就进入watiSet等待,符合条件了就被唤醒。(这里可以想到,用Condition精确唤醒也是可以的。)

class WaitNotify {
    // 等待标记
    private int flag;
    // 循环次数,为5次
    private int loopNumber;

    public WaitNotify(int flag, int loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }

    public void print(String str, int waitFlag, int nextFlag) {
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this) {
                // 能否打印
                while (flag != waitFlag) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(str);
                // 把等待标记改成下一个
                flag = nextFlag;
                this.notifyAll();
            }
        }

    }
}

运行代码

WaitNotify wn = new WaitNotify(1, 5);
new Thread(()->wn.print("a", 1, 2)).start();
new Thread(()->wn.print("b", 2, 3)).start();
new Thread(()->wn.print("c", 3, 1)).start();

ReentrantLock的Condition

看到notify,可以想到ReentrantLock的Condition也能做。

class AwaitSignal extends ReentrantLock {
    private int loopNumber;

    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void print(String str, Condition current, Condition next) {
        for (int i = 0; i < loopNumber; i++) {
            lock();
            try {
                current.await();
                System.out.print(str);
                next.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                unlock();
            }
        }
    }
}

但是调用之前,需要一个发起者,因为它们一开始全部都进入await()了。(感觉没有这么通用 ?)

AwaitSignal awaitSignal = new AwaitSignal(5);
// 精确控制abc
Condition a = awaitSignal.newCondition();
Condition b = awaitSignal.newCondition();
Condition c = awaitSignal.newCondition();
new Thread(() -> awaitSignal.print("a", a, b)).start();
new Thread(() -> awaitSignal.print("b", b, c)).start();
new Thread(() -> awaitSignal.print("c", c, a)).start();
Thread.sleep(1000);
awaitSignal.lock();
try {
    // 主线程发起,让a先打印
    a.signal();
}finally {
    awaitSignal.unlock();
}

**park和unpark **

线程级别的阻塞与唤醒,也可以操作各线程去阻塞和唤醒。

与Condition的精确唤醒有异曲同工之妙。

class ParkUnpark {
    private int loopNumber;

    public ParkUnpark(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void print(String str, Thread next) {
        for (int i = 0; i < loopNumber; i++) {
            // 让当前线程阻塞
            LockSupport.park();
            System.out.print(str);
            // 唤醒下一个线程
            LockSupport.unpark(next);
        }
    }
}

运行

static Thread t1;
static Thread t2;
static Thread t3;

public static void main(String[] args) {
    ParkUnpark pu = new ParkUnpark(5);
    // 不能直接这样干!因为t2还没出来。。。因此要提一下
    //        Thread t1 = new Thread(()->pu.print("a", t2));
    //        Thread t2 = new Thread(()->pu.print("a", t3));
    //        Thread t3 = new Thread(()->pu.print("a", t1));
    t1 = new Thread(() -> pu.print("a", t2));
    t2 = new Thread(() -> pu.print("b", t3));
    t3 = new Thread(() -> pu.print("c", t1));
    t1.start();
    t2.start();
    t3.start();
    // 发起
    LockSupport.unpark(t1);
}