Skip to content

1. 固定运行顺序

假设现在有两个线程,一个线程t1打印“hello world”,一个线程t2打印"12345",要求打印数字的线程先执行。

1.1 使用wait/notify实现

代码实现如下:

  • t1线程检测运行标志execFlag,当该标志为true时才接着运行,否则调用wait()方法放弃锁等待运行;
  • t2线程获取锁后执行任务,执行结束前将t1线程的运行标志execFlag设置为true,并且调用notifyAll()唤醒t1线程;
java
private static Object lock = new Object();
private static boolean execFlag = false;

public static void main(String[] args) {
    new Thread(()->{
        synchronized (lock){
            while (!execFlag){
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

            log.info("hello world");
        }

    },"t1").start();

    new Thread(()->{
        synchronized (lock){
            log.info("12345");
            execFlag = true;
            lock.notifyAll();
        }
    },"t2").start();
}

1.2 使用park/unpark实现

t1线程拿到锁后调用park()方法先暂停执行,直到t2线程运行结束前,调用unpark()唤醒t1线程才接着执行:

java
public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        LockSupport.park();
        log.info("hello world");
    }, "t1");
    t1.start();

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

2. 轮流执行

要求t1线程输出a,t2线程输出b,t3线程输出c,按照abc的顺序执行5次。

2.1 使用wait/notify实现

使用标志flag来指示哪个线程该运行,某个线程运行完成后,修改标志值,并且唤醒下一个线程:

java
public class Demo {
    public static void main(String[] args) {
        Task task = new Task();

        new Thread(() -> {
            task.run(5, 1, 2, "a");
        }, "t1").start();
        new Thread(() -> {
            task.run(5, 2, 3, "b");
        }, "t2").start();
        new Thread(() -> {
            task.run(5, 3, 1, "c");
        }, "t3").start();
    }
}

class Task{
    private static Object lock = new Object();

    // 1 - t1线程执行,2 - t2线程执行,3 - t3线程执行
    private static int flag = 1;

    /**
     *  循环指定次数,输出指定内容,并唤醒下一个线程开始执行
     * @param loopNum 循环次数
     * @param expectFlag 期望的标志值
     * @param nextFlag 标志下一个值
     * @param content 输出内容
     */
    public void run(int loopNum, int expectFlag, int nextFlag, String content){
        for (int i = 0; i < loopNum; i++) {
            synchronized (lock){
                while (flag != expectFlag){
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }

                System.out.print(content);
                flag = nextFlag;
                lock.notifyAll();
            }
        }
    }
}

2.2 使用await/signal实现

由于ReentrantLock支持多个条件变量,所以可以让不同的线程进入不同的条件变量,做到精确唤醒:

java
public class Demo {
    public static void main(String[] args) {

        TaskReentrantLock task = new TaskReentrantLock();

        new Thread(()-> {
            task.run(TaskReentrantLock.condition1, TaskReentrantLock.condition2, "a");
        }, "t1").start();
        new Thread(()-> {
            task.run(TaskReentrantLock.condition2, TaskReentrantLock.condition3, "b");
        }, "t2").start();
        new Thread(()-> {
            task.run(TaskReentrantLock.condition3, TaskReentrantLock.condition1, "c");
        }, "t3").start();

        Sleeper.sleep(500); // 保证t1线程已进入条件变量
        task.start();
    }
}

class TaskReentrantLock{
    private static ReentrantLock lock = new ReentrantLock();
    public static Condition condition1 = lock.newCondition();
    public static Condition condition2 = lock.newCondition();
    public static Condition condition3 = lock.newCondition();

    private int loopNum = 5;


    public void start(){
        lock.lock();

        try{
            condition1.signalAll();
        }finally {
            lock.unlock();
        }

    }

    public void run(Condition condition, Condition nextCondition, String content){
        for (int i = 0; i < loopNum; i++) {
            lock.lock();
            try {
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.print(content);
                nextCondition.signal();
            }finally {
                lock.unlock();
            }
        }
    }
}

2.3 使用park/unaprk实现

t1、t2、t3线程开始都调用park阻塞住,等待其他线程唤醒它。最开始由主线程唤醒t1线程。

java
static Thread t1 = null,t2 = null,t3 = null;

public static void main(String[] args) {

    t1 = new Thread(() -> {
        run("a",t2);
    }, "t1");

    t2 = new Thread(() -> {
        run("b",t3);
    }, "t2");

    t3 = new Thread(() -> {
        run("c",t1);
    }, "t3");

    t1.start();
    t2.start();
    t3.start();

    LockSupport.unpark(t1);
}

private static void run(String content, Thread nextThread){
    for (int i = 0; i < 5; i++) {
        LockSupport.park();
        System.out.print(content);
        LockSupport.unpark(nextThread);
    }
}