Java并发面试题之线程(二)

sleep、join、yield 方法有什么区别?

sleep 方法

在指定的毫秒数内,让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。让其他线程有机会继续执行,但它并不释放对象锁。也就是如果有synchronized 同步块,其他线程仍然不能访问共享数据。注意该方法要捕获异常。

比如有两个线程同时执行(没有 synchronized),一个线程优先级为MAX_PRIORITY ,另一个为 MIN_PRIORITY 。

  • 如果没有 sleep 方法,只有高优先级的线程执行完成后,低优先级的线程才能执行。但当高优先级的线程 #sleep(5000)后,低优先级就有机会执行了
  • 总之,sleep 方法,可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。

yield 方法

yield 方法和 sleep 方法类似,也不会释放“锁标志”,区别在于:

  • 它没有参数,即 yield 方法只是使当前线程重新回到可执行状态,所以执行yield 的线程有可能在进入到可执行状态后马上又被执行。
  • 另外 yield 方法只能使同优先级或者高优先级的线程得到执行机会,这也和 sleep 方法不同。

join 方法

Thread 的非静态方法 join让一个线程 B “加入”到另外一个线程 A 的尾部。在线程 A 执行完毕之前,线程 B 不能工作。示例代码如下:

		Thread t = new MyThread();
		t.start();
		t.join();

保证当前线程停止执行,直到该线程所加入的线程 t 完成为止。然而,如果它加入的线程 t 没有存活,则当前线程不需要停止。

线程的 sleep 方法和 yield 方法有什么区别?

  • sleep 方法给其他线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会。yield方法只会给相同优先级或更高优先级的线程以运行的机会。
  • 线程执行 sleep 方法后转入阻塞(blocked)状态,而执行 yield 方法后转入就绪(ready)状态。
  • sleep 方法声明抛出 InterruptedException 异常,而 yield 方法没有声明任何异常。
  • sleep 方法比 yield 方法(跟操作系统 CPU 调度相关)具有更好的可移植性。

为什么 Thread 类的 sleep 和 yield 方法是静态的?

Thread 类的 sleep 和 yield 方法,将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。

sleep(0) 有什么用途?

Thread#sleep(0) 方法,并非是真的要线程挂起 0 毫秒,意义在于这次调用 Thread#sleep(0) 方法,把当前线程确实的被冻结了一下,让其他线程有机会优先执行。Thread#sleep(0) 方法,是你的线程暂时放弃 CPU ,也就是释放一些未用的时间片给其他线程或进程使用,就相当于一个让位动作。

你如何确保 main 方法所在的线程是 Java 程序最后结束的线程?

我们可以使用 Thread 类的 #join() 方法,来确保所有程序创建的线程在 main 方法退出前结束。

interrupted 和 isInterrupted 方法的区别?

interrupt 方法

Thread#interrupt() 方法,用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。

注意:线程中断仅仅是置线程的中断状态位,不会停止线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出 InterruptedException 的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。

interrupted

Thread#interrupted() 静态方法,查询当前线程的中断状态,并且清除原状态。如果一个线程被中断了,第一次调用 #interrupted() 方法则返回 true ,第二次和后面的就返回 false 了。

// Thread.java

public static boolean interrupted() {
    return currentThread().isInterrupted(true); // 清理
}

private native boolean isInterrupted(boolean ClearInterrupted);

isInterrupted

Thread#isInterrupted() 方法,查询指定线程的中断状态,不会清除原状态。代码如下:

// Thread.java

public boolean isInterrupted() {
    return isInterrupted(false); // 不清楚
}

private native boolean isInterrupted(boolean ClearInterrupted);

什么叫线程安全?

线程安全,是编程中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成。

Servlet 是线程安全吗?

Servlet 不是线程安全的,Servlet 是单实例多线程的,当多个线程同时访问同一个方法,是不能保证共享变量的线程安全性的。

Struts2 是线程安全吗?

Struts2 的 Action 是多实例多线程的,是线程安全的,每个请求过来都会 new 一个新的 Action 分配给这个请求,请求完成后销毁。

SpringMVC 是线程安全吗?

不是的,和 Servlet 类似的处理流程。

单例模式的线程安全性?

例模式的线程安全意味着:某个类的实例在多线程环境下只会被创建一次出来。单例模式有很多种的写法,总结一下:

  • 饿汉式单例模式的写法:线程安全
  • 懒汉式单例模式的写法:非线程安全
  • 双检锁单例模式的写法:线程安全

多线程同步和互斥有几种实现方法,都是什么?

线程同步

线程同步,是指线程之间所具有的一种制约关系,一个线程的执行依赖另一个线程的消息,当它没有得到另一个线程的消息时应等待,直到消息到达时才被唤醒。

线程间的同步方法,大体可分为两类:用户模式和内核模式。顾名思义:

  • 内核模式,就是指利用系统内核对象的单一性来进行同步,使用时需要切换内核态与用户态。内核模式下的方法有:
    • 事件
    • 信号量
    • 互斥量
  • 用户模式,就是不需要切换到内核态,只在用户态完成操作。用户模式下的方法有:
    • 原子操作(例如一个单一的全局变量)
    • 临界区

线程互斥

线程互斥,是指对于共享的进程系统资源,在各单个线程访问时的排它性。

  • 当有若干个线程都要使用某一共享资源时,任何时刻最多只允许一个线程去使用,其它要使用该资源的线程必须等待,直到占用资源者释放该资源。
  • 线程互斥可以看成是一种特殊的线程同步

如何在两个线程间共享数据?

在两个线程间共享变量,即可实现共享。

一般来说,共享变量要求变量本身是线程安全的,然后在线程内使用的时候,如果有对共享变量的复合操作,那么也得保证复合操作的线程安全性。

怎么检测一个线程是否拥有锁?

调用 Thread#holdsLock(Object obj) 静态方法,它返回 true 如果当且仅当当前线程拥有某个具体对象的锁。代码如下:

public static native boolean holdsLock(Object obj);

10 个线程和 2 个线程的同步代码,哪个更容易写?

从写代码的角度来说,两者的复杂度是相同的,因为同步代码与线程数量是相互独立的。

但是同步策略的选择依赖于线程的数量,因为越多的线程意味着更大的竞争,所以你需要利用同步技术,如锁分离,这要求更复杂的代码和专业知识。

什么是 ThreadLocal 变量?

ThreadLocal ,是 Java 里一种特殊的变量。每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞争条件被彻底消除了

它是为创建代价高昂的对象获取线程安全的好方法,比如你可以用 ThreadLocal 让 SimpleDateFormat 变成线程安全的,因为那个类创建代价高昂且每次调用都需要创建不同的实例所以不值得在局部范围使用它,如果为每个线程提供一个自己独有的变量拷贝,将大大提高效率。

  • 首先,通过复用减少了代价高昂的对象的创建个数。
  • 其次,你在没有使用高代价的同步或者不变性的情况下获得了线程安全。
    所以,ThreadLocal 很适合实现线程级的单例。

什么是 InheritableThreadLocal ?

InheritableThreadLocal 类,是 ThreadLocal 类的子类。ThreadLocal 中每个线程拥有它自己的值,与 ThreadLocal 不同的是,InheritableThreadLocal 允许一个线程以及该线程创建的所有子线程都可以访问它保存的值。

在多线程环境下,SimpleDateFormat 是线程安全的吗?

不是,非常不幸,DateFormat 的所有实现,包括 SimpleDateFormat 都不是线程安全的,因此你不应该在多线程序中使用,除非是在对外线程安全的环境中使用,如将 SimpleDateFormat 限制在 ThreadLocal 中。

如果你不这么做,在解析或者格式化日期的时候,可能会获取到一个不正确的结果。因此,从日期、时间处理的所有实践来说,我强力推荐 joda-time 库。

你可能感兴趣的:(Java并发面试题之线程(二))