1.Thread 类中的start() 和 run() 方法有什么区别?
在Java编程语言中,Thread 类的 start() 和 run() 方法有重要的区别:
start() 方法:
- 当你调用 start() 方法时,它会启动一个新的线程,并且这个新线程会执行 run() 方法中的代码。
- start() 方法的调用会导致 JVM 创建一个系统级别的线程,这将使程序真正地并发执行。
- 一旦 start() 方法被执行,它就会立即返回给调用它的线程(即原来的线程),而新的线程则开始运行。
- 每个线程只能调用一次 start() 方法。多次调用会导致 IllegalThreadStateException。
run() 方法:
- run() 方法包含了线程需要执行的代码。它是线程的入口点,类似于主函数是程序的入口点。
- 如果你直接调用 run() 方法,它会在当前线程中像普通的方法调用一样执行,不会开启新的线程,因此也不会实现并发。
- 直接调用 run() 不会利用到多线程的优势,因为实际上没有创建新的线程来并行处理任务。
总结来说,要让线程并发运行,你应该使用 start() 方法,而不是直接调用 run() 方法。如果你只是想执行 run() 方法中的代码而不关心是否是在线程上下文中执行,那么你可以直接调用 run() 方法,但这通常不是我们使用线程的目的。
2.为什么wait, notify 和 notifyAll这些方法不在thread类里面?
wait(), notify(), 和 notifyAll() 方法并不在 Thread 类中,而是在 Object 类中定义的。这是因为这些方法与对象的内部状态监控有关,而不是直接与线程的生命周期管理相关。以下是几个原因解释为什么这些方法被设计为 Object 类的一部分:
对象锁(Monitor):
- 每个Java对象都有一个关联的锁(也称为监视器)。当线程需要对某个对象进行同步操作时,它必须首先获取该对象的锁。
- wait(), notify(), 和 notifyAll() 方法是用于协调线程间基于对象锁的通信。
条件等待:
- 这些方法允许线程在满足特定条件之前暂停执行,并释放对象锁。当条件发生变化时,可以通过 notify() 或 notifyAll() 唤醒等待中的线程。
- 因为条件通常是与对象的状态相关的,所以将这些方法放在 Object 类中可以确保它们作用于正确的对象实例上。
避免竞争条件:
- 通过让这些方法成为 Object 的一部分,可以保证任何对这些方法的调用都必须在一个已经获得相应对象锁的同步代码块或方法内进行。这有助于防止竞争条件和其他并发问题。
灵活性和复用性:
- 将这些方法放在 Object 类中意味着所有Java类都可以使用它们,无需额外实现。这样可以在任意对象上创建生产者-消费者模式或其他线程协作模式,增加了代码的灵活性和可复用性。
语义清晰:
- 把这些方法放在 Object 类里,能够更明确地表达出它们的作用范围是针对具体对象的,而不是整个线程的行为。因此,程序员可以更容易理解哪些线程在等待哪个对象的条件变化。
总之,wait(), notify(), 和 notifyAll() 方法的设计决策反映了Java对于对象级别的并发控制的关注,以及如何有效地管理和协调多个线程之间的交互。
3.为什么wait和notify方法要在同步块中调用?
wait() 和 notify() 方法必须在同步块(synchronized block)或同步方法(synchronized method)中调用,原因如下:
确保线程安全:
- 当一个线程调用对象的 wait() 方法时,它会释放该对象的锁,并进入等待状态。为了确保在线程释放锁之前对共享数据的操作已经完成并且是可见的给其他线程,必须在持有对象锁的情况下调用 wait()。
- 类似地,当一个线程调用 notify() 或 notifyAll() 时,它应该保证当前线程已经更新了所有需要改变的状态信息,并且这些更改对于其他等待中的线程是可见的。
避免非法监控器状态异常 (IllegalMonitorStateException):
- 如果一个线程试图在没有持有适当锁的情况下调用 wait(), notify(), 或 notifyAll(),将会抛出 IllegalMonitorStateException。这是因为这些方法设计为只能由拥有对象监视器(即对象锁)的线程来调用。
维护条件变量的一致性:
- wait() 和 notify() 是用来协调线程间基于特定条件的变化。例如,在生产者-消费者问题中,当缓冲区满时,生产者应该等待;当缓冲区空时,消费者应该等待。这种情况下,只有当线程持有相关对象的锁时,才能正确检查和修改条件变量,从而确保条件的一致性和正确性。
防止忙等待 (Busy Waiting):
- 在没有同步机制的情况下,线程可能会持续不断地检查某个条件是否满足,这会导致不必要的CPU资源浪费。通过使用 wait() 和 notify() 并结合同步块,可以有效地让线程在条件不满足时休眠,并在条件发生变化时被唤醒,从而提高效率。
实现正确的线程通信:
- 同步块确保了线程之间的有序通信。当一个线程调用了 wait(),它会被挂起直到另一个线程调用了 notify() 或 notifyAll()。这种方式保证了线程间的通信是按照预期进行的,而不会出现混乱或无序的情况。
综上所述,将 wait() 和 notify() 放在同步上下文中调用是为了确保操作的安全性、正确性以及高效的线程间协作。这是Java并发编程模型的一部分,旨在帮助开发者编写稳定可靠的多线程程序。
4.Java中interrupted 和 isInterruptedd方法的区别?
在Java中,interrupted() 和 isInterrupted() 方法都是用来检查线程是否被中断的,但它们之间有一些关键的区别:
Thread.interrupted():
- 这是一个静态方法。
- 它会清除当前线程的中断状态。也就是说,如果线程已经被中断了,调用此方法后,该线程的中断标志将被设置为false。
- 它只对当前线程有效,即它总是检查并可能清除调用它的那个线程的中断状态。
- 如果线程未被中断,则返回false;如果线程已被中断,则返回true,并且同时将线程的中断状态重置为未中断。
Thread.isInterrupted():
- 这是一个实例方法。
- 它不会改变线程的中断状态,只会查询线程的中断状态。因此,它可以多次调用而不会影响中断标志。
- 它可以用于任何线程实例,不仅仅是当前线程。你可以通过传递一个线程对象来检查该线程是否被中断。
- 如果线程被中断,则返回true;否则返回false,且不会修改中断状态。
示例代码
Thread thread = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {// 执行任务...}
});thread.start();
// 一些时间后...
thread.interrupt();// 检查并清除中断状态(仅适用于当前线程)
boolean wasInterrupted = Thread.interrupted(); // 返回 true 或 false 并清除中断状态// 检查其他线程的中断状态而不清除它
boolean isThreadInterrupted = thread.isInterrupted(); // 只返回 true 或 false,不改变状态
总结
- 使用 Thread.interrupted() 来检测和清除当前线程的中断状态。
- 使用 Thread.isInterrupted() 来单纯地检测某个特定线程的中断状态,而不会对其做任何改变。
理解这两个方法之间的区别对于正确处理线程中断非常重要,尤其是在编写多线程应用时。
5.Java中synchronized 和 ReentrantLock 有什么不同?
在Java中,synchronized 关键字和 ReentrantLock 类都提供了同步机制,用于控制多个线程对共享资源的访问。然而,它们之间有一些关键的不同点:
1. 使用方式
synchronized:
- 是一种内置的语言特性,使用起来非常简单。可以通过声明同步方法或同步代码块来使用。
- 同步方法是通过在方法定义前加上 synchronized 关键字来实现的;同步代码块则是通过 synchronized(object) { … } 的形式,其中 - object 是你想要同步的对象。
ReentrantLock:
- 是一个显式的锁对象,需要从 java.util.concurrent.locks 包中导入。
- 使用时需要手动获取锁(lock() 方法)和释放锁(unlock() 方法)。通常会结合 try-finally 或者 Java 7 引入的 try-with-resources 来确保锁最终会被释放。
2. 功能特性
synchronized:
- 自动管理锁的获取与释放:当线程进入同步代码块或方法时自动获得锁,退出时自动释放锁。
- 支持可重入锁:如果同一个线程再次请求已经持有的锁,不会导致死锁。
- 没有提供尝试锁定、定时锁定等高级功能。
ReentrantLock:
- 提供了更多的灵活性和更强大的功能,比如:
- 尝试锁定(tryLock()),允许线程尝试获取锁而不被阻塞。
- 定时锁定(tryLock(long time, TimeUnit unit)),可以在指定时间内等待锁。
- 可中断的锁定(lockInterruptibly()),使等待中的线程可以响应中断。
- 支持公平锁策略,即可以选择按照请求顺序分配锁。
- 允许绑定多个条件(Condition 对象),这比 synchronized 块中的单一等待集更加灵活。
3. 性能差异
- 在早期版本的Java中,synchronized 的性能往往不如 ReentrantLock,因为 synchronized 需要依赖于JVM级别的锁膨胀机制。不过,在Java 6及之后的版本中,synchronized 的性能得到了极大的优化,很多情况下它的性能与 ReentrantLock 相当,甚至更好。
4. 锁的粒度
- synchronized 的粒度通常是整个方法或代码块,而 ReentrantLock 可以提供更细粒度的控制,因为它允许程序员精确地决定在哪里获取和释放锁。
结论
选择 synchronized 还是 ReentrantLock 取决于具体的需求。如果你只需要简单的同步,并且不需要上述提到的 ReentrantLock 的额外特性,那么 synchronized 是一个简洁的选择。但是,如果你需要更复杂的锁定行为或者更好的控制,那么 ReentrantLock 可能是一个更好的选择。