跳转至

深入理解Java中的等待机制(How to Wait in Java)

简介

在Java多线程编程中,wait机制是一个非常重要的概念。它允许线程暂停执行,直到其他线程对该对象调用notifynotifyAll方法。这一机制在协调多个线程的执行顺序、避免资源竞争等方面发挥着关键作用。本文将深入探讨Java中wait的基础概念、使用方法、常见实践以及最佳实践,帮助读者全面掌握这一重要的多线程控制技术。

目录

  1. 基础概念
  2. 使用方法
    • wait()方法
    • wait(long timeout)方法
    • wait(long timeout, int nanos)方法
  3. 常见实践
    • 生产者 - 消费者模型
    • 线程同步与资源共享
  4. 最佳实践
    • 避免死锁
    • 合理设置等待时间
  5. 小结
  6. 参考资料

基础概念

在Java中,wait方法是定义在Object类中的实例方法。每个对象都有一个与之关联的监视器(monitor),也称为锁。当一个线程访问被synchronized关键字修饰的代码块或方法时,它会获取该对象的锁。

wait方法用于使当前线程等待,直到其他线程调用该对象的notifynotifyAll方法。调用wait方法时,当前线程会释放对象的锁,进入等待状态,直到被唤醒。这就允许其他线程获取该对象的锁并对其进行操作。

使用方法

wait()方法

这是最基本的wait方法调用形式。调用该方法后,当前线程会释放对象的锁,并进入等待状态,直到其他线程调用该对象的notifynotifyAll方法。

public class WaitExample {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("Thread 1 starts waiting...");
                    lock.wait();
                    System.out.println("Thread 1 has been awakened...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread thread2 = new Thread(() -> {
            synchronized (lock) {
                System.out.println("Thread 2 is doing some work...");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Thread 2 notifies Thread 1...");
                lock.notify();
            }
        });

        thread1.start();
        thread2.start();
    }
}

wait(long timeout)方法

这个方法允许指定一个最长等待时间(以毫秒为单位)。如果在指定的时间内没有其他线程调用notifynotifyAll方法,那么当前线程会自动唤醒。

public class WaitTimeoutExample {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("Thread 1 starts waiting with timeout...");
                    lock.wait(5000);
                    System.out.println("Thread 1 has been awakened or timeout reached...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
    }
}

wait(long timeout, int nanos)方法

此方法在wait(long timeout)的基础上,增加了对纳秒级别的时间控制。它允许更精确地指定等待时间。

public class WaitNanosExample {
    public static void main(String[] args) {
        Object lock = new Object();

        Thread thread1 = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("Thread 1 starts waiting with nanos timeout...");
                    lock.wait(2000, 500000000);
                    System.out.println("Thread 1 has been awakened or timeout reached...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        thread1.start();
    }
}

常见实践

生产者 - 消费者模型

生产者 - 消费者模型是多线程编程中的经典示例,waitnotify机制在其中起到了关键作用。生产者线程生产数据并将其放入共享缓冲区,消费者线程从缓冲区中取出数据进行处理。当缓冲区满时,生产者线程需要等待;当缓冲区为空时,消费者线程需要等待。

import java.util.LinkedList;
import java.util.Queue;

class Producer implements Runnable {
    private final Queue<Integer> queue;
    private final int capacity;

    public Producer(Queue<Integer> queue, int capacity) {
        this.queue = queue;
        this.capacity = capacity;
    }

    @Override
    public void run() {
        int value = 0;
        while (true) {
            synchronized (queue) {
                while (queue.size() == capacity) {
                    try {
                        System.out.println("Queue is full, Producer is waiting...");
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                System.out.println("Producing value: " + value);
                queue.add(value++);
                queue.notify();
            }
        }
    }
}

class Consumer implements Runnable {
    private final Queue<Integer> queue;

    public Consumer(Queue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (queue) {
                while (queue.isEmpty()) {
                    try {
                        System.out.println("Queue is empty, Consumer is waiting...");
                        queue.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                int value = queue.poll();
                System.out.println("Consuming value: " + value);
                queue.notify();
            }
        }
    }
}

public class ProducerConsumerExample {
    public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        int capacity = 5;

        Thread producerThread = new Thread(new Producer(queue, capacity));
        Thread consumerThread = new Thread(new Consumer(queue));

        producerThread.start();
        consumerThread.start();
    }
}

线程同步与资源共享

在多个线程共享资源的场景下,waitnotify机制可以用于确保资源的正确访问和同步。例如,多个线程需要访问一个共享的数据库连接池,当连接池中的连接耗尽时,线程需要等待,直到有连接被释放并返回连接池。

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

class ConnectionPool {
    private final BlockingQueue<Object> connections;

    public ConnectionPool(int size) {
        connections = new ArrayBlockingQueue<>(size);
        for (int i = 0; i < size; i++) {
            connections.add(new Object());
        }
    }

    public Object getConnection() {
        try {
            return connections.take();
        } catch (InterruptedException e) {
            e.printStackTrace();
            return null;
        }
    }

    public void releaseConnection(Object connection) {
        connections.add(connection);
    }
}

class Worker implements Runnable {
    private final ConnectionPool pool;

    public Worker(ConnectionPool pool) {
        this.pool = pool;
    }

    @Override
    public void run() {
        Object connection = pool.getConnection();
        if (connection != null) {
            try {
                System.out.println(Thread.currentThread().getName() + " acquired connection");
                // 模拟使用连接进行工作
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                pool.releaseConnection(connection);
                System.out.println(Thread.currentThread().getName() + " released connection");
            }
        }
    }
}

public class ConnectionPoolExample {
    public static void main(String[] args) {
        ConnectionPool pool = new ConnectionPool(3);

        Thread worker1 = new Thread(new Worker(pool), "Worker1");
        Thread worker2 = new Thread(new Worker(pool), "Worker2");
        Thread worker3 = new Thread(new Worker(pool), "Worker3");
        Thread worker4 = new Thread(new Worker(pool), "Worker4");

        worker1.start();
        worker2.start();
        worker3.start();
        worker4.start();
    }
}

最佳实践

避免死锁

死锁是多线程编程中常见的问题,当两个或多个线程相互等待对方释放锁时,就会发生死锁。为了避免死锁,应该遵循以下原则: - 尽量减少锁的使用范围,只在必要的代码块中使用synchronized关键字。 - 按照固定的顺序获取锁,避免不同线程以不同顺序获取锁。 - 使用tryLock方法来尝试获取锁,避免无限期等待。

合理设置等待时间

在使用带有超时参数的wait方法时,应该根据具体业务需求合理设置等待时间。过短的等待时间可能导致线程频繁唤醒和重试,增加系统开销;过长的等待时间可能导致响应不及时,影响系统性能。

小结

本文深入探讨了Java中的wait机制,包括其基础概念、使用方法、常见实践以及最佳实践。通过学习这些内容,读者应该能够在多线程编程中熟练运用wait机制,实现线程之间的有效同步和协调,避免常见的多线程问题,提高程序的性能和稳定性。

参考资料