文章目录

  • 线程间的协作
  • sleep(long millis)
  • yield( )
  • wait() / notify()
  • 等待 / 通知的经典范式
  • 等待 / 超时模式
  • join()


线程间的协作

sleep(long millis)

Thread.sleep(),让当前线程暂停等待时间,让出CPU的使用权,不释放锁。

yield( )

让出当前的CPU时间片,将线程从运行改为就绪,并且再次参与CPU使用权的竞争。也不会释放锁。

wait() / notify()

等待 / 通知机制: 线程A获得了的对象O的锁,调用了对象O的wait方法,进入等待状态,并且释放掉对象O的锁。线程B在获得对象O的锁之后,调用对象O的notify() / notifyAll()方法,通知所有在对象O上的等待对象苏醒。在线程A再次获取到了对象O的锁之后,线程A苏醒,开始继续运行。

两个线程通过对象O来完成交互。

wait(long millis):等待N毫秒,在时间过了之后,没有通知就直接返回。

public class WaitNotifyThread {

    private static final Object lock = new Object();

    private static boolean flag = true;

    private static final SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

    static class WaitThread implements Runnable {

        @Override
        public void run() {
            // wait线程首先要拿到对象lock的锁
            synchronized (lock) {
                System.out.println("wait线程拿到锁,当前时间:" + sdf.format(new Date()));
                while (flag) {

                    try {
                        System.out.println("当前标识为true,wait线程等待中,当前时间:" + sdf.format(new Date()));
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("wait线程被唤醒,标识为" + flag + ",当前时间:" + sdf.format(new Date()));
            }
        }
    }

    static class NotifyThread implements Runnable {

        @Override
        public void run() {
            // 调用notify前,必须对该对象加锁
            synchronized (lock) {
                flag = false;
                System.out.println("notify线程通知所有等待线程唤醒,当前时间:" + sdf.format(new Date()));
                lock.notify();

                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    public static void main(String[] args) {
        Thread waitThread = new Thread(new WaitThread());
        waitThread.start();

        Thread notifyThread = new Thread(new NotifyThread());
        notifyThread.start();
    }
}

输出结果:

wait线程拿到锁,当前时间:16:30:07
当前标识为true,wait线程等待中,当前时间:16:30:07
notify线程通知所有等待线程唤醒,当前时间:16:30:07
wait线程被唤醒,标识为false,当前时间:16:30:09
  1. 使用wait()、notify()和notifyAll()时需要对调用对象先加锁。
  2. 调用wait()方法后,线程状态由运行(RUNNING)改为等待(WAITING),释放对象的锁,进入对象的等待队列。
  3. notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notifyAll()的线程释放锁之后,等待线程才有机会从wait()返回。
  4. notify()方法将等待队列中的一个等待线程移到同步队列,notifyAll()方法将等待队列里的所有线程都移到同步队列中。被移动的线程状态从等待(WAITING)变为阻塞(BLOCKED).
  5. 从wait()方法返回的前提是获得了调用对象的锁。

Sleep()和wait的区别:

  • sleep是让出CPU执行时间,睡眠一会,当时间到就会苏醒,而wait则是交出当前持有对象的锁,然后进入等待状态。
  • sleep不会释放锁,wait会释放锁。
  • sleep在任何地方都可使用,wait只能放在同步代码块

sleep和yeild的区别:

  • sleep让出CPU时不考虑线程的优先级,而yield会考虑线程的优先级,优先给优先级高的线程执行。
  • sleep之后,线程会进入等待状态,在指定时间内一定不会执行,而yield只是让出CPU执行时间,会再次参与到CPU竞争中。

等待 / 通知的经典范式

等待方(消费者):

  1. 获取对象的锁
  2. 如果条件不满足,调用wait()方法等待
  3. 条件满足则执行对应的逻辑
synchronized (对象) {
    while (条件不满足) {
    	对象.wait();
    }
    执行对应的处理逻辑
}

通知方(生产者):

  1. 获得对象的锁。
  2. 改变条件
  3. 调用所有等待在对象上的线程:notifyAll()
synchroized (对象) {
    改变条件
    对象.notifyAll();
}

例,一个符合生产者和消费者问题的程序:

  • 对某一个对象(枪膛)进行操作,其最大容量是20颗子弹,
  • 生产者线程(通知方)是一个压入线程,它不断向枪膛中压入子弹,
  • 消费者线程(等待方)是一个射出线程,它不断从枪膛中射出子弹。
public class WaitNotifyThreadDemo {

    private static final Stack<Integer> STACK = new Stack<>();

    private static int MAX_SIZE = 20;

    // 生产者
    static class Producer implements Runnable {
        @Override
        public void run() {
            // 不断压入子弹
            while (true) {
                synchronized (STACK) {
                    if (STACK.empty() || STACK.size() < MAX_SIZE) {
                        STACK.push(1);
                        System.out.println("生产者向枪膛压入1个子弹,当前子弹数为:" + STACK.size());
                    }
                    // 通知消费者,可以射击了。
                    STACK.notify();
                }

                try {
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static class Customer implements Runnable {
        @Override
        public void run() {
            // 不断射出子弹
            while (true) {
                synchronized (STACK) {
                    while (STACK.isEmpty()) {
                        try {
                            // 子弹不满,暂时等待。
                            System.out.println("当前弹夹不满,消费者等待中。。。");
                            STACK.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }

                    }
                    STACK.pop();
                    System.out.println("消费者从枪膛射出1个子弹,当前子弹数为:" + STACK.size());
                }
                try {
                    Thread.sleep(new Random().nextInt(900));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        }
    }

    public static void main(String[] args) {
        Thread customerThread = new Thread(new Customer());
        customerThread.start();

        Thread producerThread = new Thread(new Producer());
        producerThread.start();
    }
}

输出结果:

当前弹夹不满,消费者等待中。。。
生产者向枪膛压入1个子弹,当前子弹数为:1
消费者从枪膛射出1个子弹,当前子弹数为:0
生产者向枪膛压入1个子弹,当前子弹数为:1
消费者从枪膛射出1个子弹,当前子弹数为:0
当前弹夹不满,消费者等待中。。。
生产者向枪膛压入1个子弹,当前子弹数为:1
消费者从枪膛射出1个子弹,当前子弹数为:0
当前弹夹不满,消费者等待中。。。
生产者向枪膛压入1个子弹,当前子弹数为:1
消费者从枪膛射出1个子弹,当前子弹数为:0

等待 / 超时模式

等待 / 超时模式是等待通知模式的升级,在等待通知模式中,如果条件不满足,就一直在循环。对于等待超时模式,给一个超时的时间,在这段时间内,能返回结果,将结果直接返回,超出这段时间,就返回默认结果。

假如超时时间为T,那么在now + T后,程序返回。定义如下变量:

  • 等待持续时间:REMAINING = T
  • 超时时间:FUTURE = now + T
// 等待超时模式伪代码
public synchronized Object get(long mills) {
    // 超时时间:当前时间 + 等待时间
    long future = System.currentTimeMills() + mills;
    long remaining = mills;
    while (条件不满足 && remaining > 0) {
        wait(remaining);
        remaining = future - System.currentTimeMills(); 
    }
    return result;
}

等待超时模式增加了时间的控制,即使方法执行时间过长,也不会永久阻塞调用者,而是会按照调用者的要求按时返回。

等待超时模式模拟获取数据库连接:

public class WaitNotifyOvertime {

    static class DbConnection {

        // 线程休眠0.5秒,模拟业务操作
        void doSomeThing() {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    static class ConnectionPool {
        private final LinkedList<DbConnection> dbConnectionPool = new LinkedList<>();

        // 初始化连接池
        public ConnectionPool(int initSize) {
            for (int i = 0; i < initSize; i++) {
                dbConnectionPool.addLast(new DbConnection());
            }
        }

        // 释放连接池资源
        public void releaseConnection (DbConnection dbConnection) {
            if (dbConnection != null) {
                synchronized (dbConnectionPool) {
                    dbConnectionPool.addLast(dbConnection);
                    dbConnectionPool.notifyAll();
                }
            }
        }

        // 从连接池中抓取资源,超时返回
        public DbConnection fenchConnection (long millis) {
            synchronized (dbConnectionPool) {
                // 超时时间小于0,表示一直等待
                if (millis <= 0) {
                    while (dbConnectionPool.isEmpty()) {
                        try {
                            dbConnectionPool.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    return dbConnectionPool.removeFirst();
                } else {
                    // 超时时间
                    Long future = System.currentTimeMillis() + millis;

                    // 超时等待时间
                    Long waiting = millis;

                    while (dbConnectionPool.isEmpty() && waiting > 0) {
                        try {
                            dbConnectionPool.wait(waiting);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
						// 被唤醒后,重新计算等待时间
                        waiting = future - System.currentTimeMillis();
                    }

                    // 这种是数据库连接池有连接了,下面的情况是等待时间到了。
                    if ( !dbConnectionPool.isEmpty()) {
                        return dbConnectionPool.removeFirst();
                    }
                    return null;
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        // 初始化连接池,里面有10个连接
        ConnectionPool connectionPool = new ConnectionPool(10);

        AtomicInteger got = new AtomicInteger(0);
        AtomicInteger notGot = new AtomicInteger(0);

        CountDownLatch countDownLatch = new CountDownLatch(30);

        // 30个线程需要连接池里的连接
        int taskSize = 30;

        for (int i = 0; i < taskSize; i++) {
            new Thread(() -> {
                int count = 20;
                // 每个线程尝试拿20次,一共拿600次
                while (count > 0) {
                    DbConnection dbConnection = connectionPool.fenchConnection(1000);
                    if (dbConnection != null) {
                        try {
                            got.incrementAndGet();
                            dbConnection.doSomeThing();
                        } finally {
                            connectionPool.releaseConnection(dbConnection);
                        }
                    } else {
                        notGot.incrementAndGet();
                        System.out.println(Thread.currentThread().getName() + "获取连接失败。");
                    }
                    count--;
                }

                countDownLatch.countDown();
            }).start();
        }

        countDownLatch.await();

        System.out.println("成功获得连接次数:" + got.get());
        System.out.println("未成功获得连接的线程数:" + notGot.get());
    }
}

执行结果:

Thread-15获取连接失败。
Thread-19获取连接失败。
成功获得连接次数:348
未成功获得连接的线程数:252

join()

如果一个线程A执行了thread.join()方法,含义是:当前线程A等待thread线程终止之后,从thread.join()返回。

join(long millis):如果在给定的时间内,线程thread没有执行完,那么就直接返回。

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

        Thread previous = Thread.currentThread();

        for (int i = 0; i < 5; i++) {
            // 每次调用前一个线程的join()方法,等前一个线程执行完再执行.
            Thread thread = new Thread(new Runner(previous),String.valueOf(i));
            thread.start();

            previous = thread;
        }

        System.out.println(Thread.currentThread().getName() + " terminate.");
    }

    static class Runner implements Runnable {

        private Thread thread;

        public Runner(Thread thread) {
            this.thread = thread;
        }

        @Override
        public void run() {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " terminate.");
        }
    }
}

输出结果:

main terminate.
0 terminate.
1 terminate.
2 terminate.
3 terminate.
4 terminate.

join()方法也满足等待 / 通知模型,当前线程等待前一个线程执行。前一个线程执行完后,调用线程本身的notifyAll()方法,唤醒所有在该线程等待的线程。

加锁;条件不满足一直等待;条件满足方法返回。

public final synchronized void join(long millis){
    if (millis == 0) {
        // 条件不满足,一直等待
        while (isAlive()) {
            wait(0);
        }
        // 条件满足,方法返回
    }
}