天天看点

Java面试题库(线程安全)

作者:技能引路者

1. sleep和wait区别

来源不同:sleep是Thread类中的静态方法,而wait是Object类中的实例方法。

使用方式不同:sleep不需要和synchronized关键字一起使用,直接调用即可。而wait必须在synchronized块中调用,且必须拥有synchronized锁定的对象的锁,否则会抛出IllegalMonitorStateException异常。

释放锁的不同:当线程调用sleep方法时,不会释放锁,线程仍然占有锁。而当线程调用wait方法时,会释放对象的锁,进入等待状态。只有当其他线程调用notify或notifyAll方法并且该线程获得了锁时,线程才能从wait状态中解除。

调用方式不同:wait的调用可以通过notify或notifyAll来解除,而sleep则需要等待指定的时间过去后自动结束。

2. 如何中断一个线程的执行?interrupt方法有什么作用?

Java 中断一个线程的执行可以通过调用 Thread 类提供的 interrupt() 方法来实现,该方法有以下作用:

中断线程的阻塞状态:如果线程正在调用 Object.wait()、Thread.sleep() 或者 Thread.join() 等方法进入阻塞状态,那么调用该线程的 interrupt() 方法将会使线程抛出 InterruptedException 异常,从而中断线程的阻塞状态。

中断线程的运行状态:如果线程处于运行状态,那么调用该线程的 interrupt() 方法将会使线程的中断标志(interrupt status)被置位,表明该线程已经被中断了。

需要注意的是,调用 interrupt() 方法只是向线程发送中断信号,如果线程不在响应中断信号的情况下,该线程仍然可以继续执行。因此,开发者需要在线程的执行过程中定期检查线程的中断状态,并且在合适的时候退出线程的执行。

3. 如何保证线程安全?

线程安全可以通过同步机制来保证。可以使用synchronized关键字对代码块或方法进行同步,或者使用Lock接口的实现类对代码块或方法进行同步。此外,还可以使用线程安全的集合类,如ConcurrentHashMap、ConcurrentLinkedQueue等。

4. 如何在多线程环境中保证线程的执行顺序?

在多线程环境中保证线程的执行顺序可以使用 synchronized 关键字或 Lock 接口的锁定机制来保证线程的有序执行。

使用 CountDownLatch 或 CyclicBarrier 等同步工具来协调线程执行顺序,这些工具可以让一个或多个线程等待其他线程的操作完成以后再执行。

使用 join() 方法在一个线程中等待其他线程完成后再执行下一步操作。

使用 wait() 和 notify()/notifyAll() 方法实现线程之间的通信和协调,确保线程按照指定的顺序执行。

5. Java中有哪些锁?

Java中常用的锁有synchronized锁、ReentrantLock锁和StampedLock锁。synchronized锁是Java中最基本的锁机制,而ReentrantLock锁和StampedLock锁则提供了更高级别的功能。

6. synchronized和Lock有什么区别?它们都能用于什么情况?

synchronized是Java内置的锁机制,而Lock是Java提供的一个接口,提供了更多的灵活锁定机制。synchronized锁定粒度比较粗,只能锁定整个方法或代码块,而Lock可以控制锁的范围。

7. 什么是死锁?如何避免死锁?

死锁是指两个或两个以上的线程互相等待对方释放锁,从而导致程序无法继续执行的现象。死锁是一种非常严重的线程安全问题。

避免死锁可以采用以下一些策略:

  • 避免使用多个锁;
  • 按照相同的顺序获取锁;
  • 使用定时锁等待;
  • 使用死锁检测和恢复工具。

8. 什么是线程安全的集合?ConcurrentHashMap如何实现线程安全?

线程安全的集合是指支持在多线程环境下安全地并发访问的集合类。在Java中,常用的线程安全的集合类有ConcurrentHashMap、ConcurrentLinkedQueue、CopyOnWriteArrayList等。

ConcurrentHashMap是线程安全的HashMap实现,它采用了分段锁机制来保证线程安全。具体来说,ConcurrentHashMap将整个HashMap分成多个段,每个段使用一个锁来控制并发访问,不同的线程可以同时访问不同段的数据,从而提高了并发处理性能。

9. Stream并行执行是否线程安全?

Stream并行执行是线程安全的,但需要保证Stream中的操作是无状态的。这意味着,Stream中的每个操作都不会依赖于前面的操作结果,而且每个操作对同一个元素的结果是一样的。

10. 如何实现可重入锁?

可重入锁可以通过在锁中维护一个owner变量,记录当前持有锁的线程,然后在获取锁时,判断当前线程是否已经持有锁,如果已经持有,则可以直接返回,否则需要先等待其他线程释放锁后再获取。ReentrantLock是一种可重入锁的实现。

11. Java中的读写锁

Java读写锁(ReadWriteLock)是一种特殊的锁机制。它与普通的锁最大的不同在于,读写锁中有两种锁,分别是读锁和写锁。在读写锁中,多个线程可以同时持有读锁,但是只有一个线程可以持有写锁。读写锁的主要目的是优化对于程序中存在大量读操作但很少写操作的情况,从而提高程序的执行效率。

在ReadWriteLock中,可以通过以下方法获取或释放锁:

readLock():获取读锁;

writeLock():获取写锁;

unlock():释放锁。

12. Java中的ThreadLocal有什么作用?如何使用它?

ThreadLocal是一种提供线程局部变量的机制。它可以让每个线程都有自己的变量副本副本,从而避免了多线程情况下变量竞争的问题,保证了线程安全。

通过ThreadLocal实现线程间变量的隔离,每个线程可以单独控制自己的变量,对于其他线程的变量访问不受影响。

在Java中,可以通过ThreadLocal类创建ThreadLocal变量,并通过调用它的set()和get()方法来设置和获取当前线程的变量副本。

13. 什么是守护线程?它和普通线程有什么区别?

守护线程是一种特殊的线程,它的存在不会阻止JVM的退出。当JVM中只有守护线程时,JVM会自动退出。

普通线程则是一种用户线程,线程执行完毕后会等待其他线程结束,如果全部线程都结束后JVM仍未退出,则JVM会等待非守护线程执行完毕后再退出。

14. 什么是volatile关键字?它有什么作用?

volatile是一种线程同步机制,用于保证共享变量的可见性和禁止指令重排序。

如果某个变量被声明为volatile,则任何线程对此变量的修改都会立即更新到主内存,任何线程读取该变量时都会读取到最新的值。

volatile还可以禁止指令重排序,保证指令执行的顺序与程序中的顺序一致,从而避免由于指令重排序而导致的线程安全问题。

15. 如何保证线程的同步?如何避免线程的竞态条件?

使用synchronized关键字实现互斥锁,保证多个线程对共享资源的访问是互斥的。

使用volatile关键字保证共享变量的可见性和有序性。

使用Lock类实现更细粒度的锁,避免使用synchronized带来的性能问题和死锁等问题。

使用Atomic类提供的原子操作,避免数据竞争等问题。

通过对多个共享资源进行排序,避免出现死锁等情况。

继续阅读