控制线程执行顺序

wait notify

每次进来携带能够执行的标识, 如果标识不对则线程进入等待, 这里是公用一个 TestSyncWaitNotify对象, 所以这个对象的flag属性就是临界值,对对象加锁, 保证对象的flag属性不会同时被多个线程持有, 每一次执行执行结束需要使用notifyAll() 唤醒所有等待的线程, 然后每个线程通过竞争锁进入到 while 循环中 进行 flag 的比较.

@Slf4j(topic = "c.SyncWaitNotify")
public class TestSyncWaitNotify {
    private int flag;
    private int loopNumber;

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

    private void print(int waitFlag, int nextFlag, String str) {
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this) {
                while (this.flag != waitFlag) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        log.debug("exec wait occur");
                    }
                }
                log.debug(str);
                this.flag = nextFlag;
                this.notifyAll();
            }
        }
    }

    public static void main(String[] args) {
        TestSyncWaitNotify syncWaitNotify = new TestSyncWaitNotify(1, 5);
        new Thread(() -> syncWaitNotify.print(1, 2, "a"), "t1").start();
        new Thread(() -> syncWaitNotify.print(2, 3, "b"), "t2").start();
        new Thread(() -> syncWaitNotify.print(3, 1, "c"), "t3").start();
    }

}

park unpark

park 相对于 wait 的线程来说, 可以通过 LockSupport.unpark(thread) 唤醒指定的线程

@Slf4j(topic = "c.SyncPark")
public class TestSyncPark {
    private int loopNumber;
    private Thread[] threads;

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

    public void setThreads(Thread... threads) {
        this.threads = threads;
    }

    private void print(String str) {
        for (int i = 0; i < loopNumber; i++) {
            LockSupport.park();
            log.debug(str);
            LockSupport.unpark(nextThread());
        }
    }

    private Thread nextThread() {
        int current = 0;
        Thread currentThread = Thread.currentThread();
        for (int i = 0; i < threads.length; i++) {
            if (threads[i] == currentThread) {
                current = i < threads.length - 1 ? i + 1 : 0;
                break;
            }
        }
        return threads[current];
    }

    private void start() {
        for (Thread thread : threads) {
            thread.start();
        }
        LockSupport.unpark(threads[0]);
    }

    public static void main(String[] args) {
        TestSyncPark syncPark = new TestSyncPark(5);
        Thread t1 = new Thread(() -> syncPark.print("a"));
        Thread t2 = new Thread(() -> syncPark.print("b"));
        Thread t3 = new Thread(() -> syncPark.print("c"));

        syncPark.setThreads(t1, t2, t3);
        syncPark.start();
    }
}

await signal

await signal的方式和park相似的是都可以唤醒指定的线程, 只不过 await 是通过 condition 来进行控制的, 这里需要注意的是 condition的 await 和 signal 方法都是需要在加锁 lock() 下执行

@Slf4j(topic = "c.AwaitSignal")
public class TestAwaitSignal extends ReentrantLock {

    private int loopNumber;

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

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

    private void start(Condition current) {
        this.lock();
        try {
            log.debug("start");
            current.signal();
        } finally {
            this.unlock();
        }
    }

    public static void main(String[] args) { 
        TestAwaitSignal awaitSignal = new TestAwaitSignal(5);
        Condition aWaitSet = awaitSignal.newCondition();
        Condition bWaitSet = awaitSignal.newCondition();
        Condition cWaitSet = awaitSignal.newCondition();

        new Thread(() -> awaitSignal.print("a", aWaitSet, bWaitSet)).start();
        new Thread(() -> awaitSignal.print("b", bWaitSet, cWaitSet)).start();
        new Thread(() -> awaitSignal.print("c", cWaitSet, aWaitSet)).start();

        awaitSignal.start(aWaitSet);
    }
}

ReentrantLock

上面的 await, signal 使用到了 reentrantLock 类, 这里就简单记录一下

  • new ReentrantLock(fire: boolean) 参数默认为false: 不公平的, 可以手动设置为 true 则为公平锁

  • 锁可以被打断, 锁时使用 lock.lockInterruptibly() 如果没有竞争那么此方法就会获得 lock 锁, 如果有竞争锁就会进入阻塞队列, 但是可以被别的线程通过 interrupted方法打断

  • lock.lock()普通加锁的方式, 没有啥特点

  • lock.tryLock(timeout): boolean 携带超时时间的获取锁, 返回 boolean 值

锁对象和锁类的情况

锁对象

多个线程操作同一个对象的某个属性, 这个属性也可以被叫做操作的临界区

@Slf4j(topic = "c.ExerciseSell")
public class ExerciseSell {

    // random 是线程安全类
    static Random random = new Random();

    private static int randomAmount() {
        return random.nextInt(5) + 1;
    }

    public static void main(String[] args) throws InterruptedException {
        TicketWindow ticketWindow = new TicketWindow(1000);
        List<Thread> threadList = new ArrayList<>();
        List<Integer> amountList = new Vector<>();

        for (int i = 0; i < 2000; i++) {
            Thread thread = new Thread(() -> {
                try {
                    TimeUnit.SECONDS.sleep(randomAmount());
                    int amount = ticketWindow.sellTicket(randomAmount());
                    amountList.add(amount);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
            threadList.add(thread);
        }

        threadList.forEach(Thread::start);
        for (Thread thread : threadList) {
            thread.join();
        }

        // 21:10:21.595 [main] DEBUG c.ExerciseSell - 余票: 1
        log.debug("余票: {}", ticketWindow.getCount());
        // 21:10:21.599 [main] DEBUG c.ExerciseSell - 卖出的票: 999
        log.debug("卖出的票: {}", amountList.stream().mapToInt(Integer::intValue).sum());
        // 21:10:21.599 [main] DEBUG c.ExerciseSell - 总票数: 1000
        log.debug("总票数: {}", ticketWindow.getCount() + amountList.stream().mapToInt(Integer::intValue).sum());
    }
}

class TicketWindow {
    private int count;

    public TicketWindow(int count) {
        this.count = count;
    }

    public int getCount() {
        return count;
    }

    /**
     * sell ticket
     * @param amount 买的票数
     * @return 成功购买的票数
     */
    public synchronized int sellTicket(int amount) {
        if (this.count > amount) {
            this.count -= amount;
            return amount;
        }
        return 0;
    }
}

锁类

多个线程操作类的属性也就是静态, 或则多个类(相同的类)的相同属性, 怎么理解这句话呢? 其实就是假设有个Account类它有一个money的属性, 这时有个成员方法参数也是Account类的对象, 这时在多线程中调用时, 共享的变量就变成了this 和 参数Account, 所以需要锁this和参数Account的共享变量也就是Account类

@Slf4j(topic = "c.ExerciseTransfer")
public class ExerciseTransfer {
    // random 是线程安全类
    static Random random = new Random();

    private static int randomAmount() {
        return random.nextInt(5) + 1;
    }

    public static void main(String[] args) throws InterruptedException {
        Account a = new Account(1000);
        Account b = new Account(1000);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                a.transfer(b, randomAmount());
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                b.transfer(a, randomAmount());
            }
        });

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        // 21:32:06.119 [main] DEBUG c.ExerciseTransfer - total: 2000
        log.debug("total: {}", a.getMoney() + b.getMoney());
    }
}

class Account {
    private int money; // 被多个线程所共享

    public Account(int money) {
        this.money = money;
    }

    public int getMoney() {
        return money;
    }

    public void setMoney(int money) {
        this.money = money;
    }

    public void transfer(Account target, int money) {
        // 因为同时操作了 this.money 和 target.money 两个对象的 money 属性所以单独锁对象是锁不住的
        // 当然如果你想锁两个对象, 这个也是不行的很容易出现死锁
        synchronized (Account.class) {
            if (this.money > money) {
                this.setMoney(this.getMoney() - money);
                target.setMoney(target.getMoney() + money);
            }
        }
    }
}

生产者消费者模型-消息队列

生产者消费者模型就是一个线程负责生产, 一个线程负责消费, 而消息队列不过是多了一个容器存放消息, 容器中没有了消息就通知生产者生产消息, 满了就生产者就wait, 消费者相反, 当然这里线程间的通信方式就可以用到上面的三个api

@Slf4j(topic = "c.MessageQueue")
public class MessageQueue {
    // 消息队列集合
    private LinkedList<Message> messageLinkedList = new LinkedList<>();
    // 消息的容量
    private int capacity;

    public MessageQueue(int capacity) {
        this.capacity = capacity;
    }

    // 获取消息
    public Message task() {
        synchronized (messageLinkedList) {
            while (messageLinkedList.isEmpty()) {
                try {
                    log.debug("队列为空, 消费者线程进入等待...");
                    messageLinkedList.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Message message = messageLinkedList.poll();
            log.debug("消费了一条消息: {}", message);
            messageLinkedList.notifyAll();
            return message;
        }
    }

    // 存入消息
    public void put(Message message) {
        synchronized (messageLinkedList) {
            while (messageLinkedList.size() == capacity) {
                try {
                    log.debug("队列已满, 生产者线程进入等待...");
                    messageLinkedList.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.messageLinkedList.addLast(message);
            log.debug("已生产一条消息: {}", message);
            messageLinkedList.notifyAll();
        }
    }

    public static void main(String[] args) {
        MessageQueue queue = new MessageQueue(2);

        // 开启三个线程进行生产消息
        for (int i = 0; i < 3; i++) {
            int id = i;
            new Thread(() -> {
                queue.put(new Message(id, "value: " + id));
            }, "生产者" + id).start();
        }

        new Thread(() -> {
            while (true) {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                queue.task();
            }
        }, "消费者").start();
    }
}

class Message<T> {
    private int id;
    private T value;

    public Message(int id, T value) {
        this.id = id;
        this.value = value;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", value=" + value +
                '}';
    }
}

保护性暂停

posted on 2022-03-01 22:18  莫铠瑞  阅读(49)  评论(0)    收藏  举报