ThinkChat🤖让你学习和工作更高效,注册即送10W Token,即刻开启你的AI之旅 广告
### Daemon(守护线程) Java 中有两类线程:User Thread (用户线程)、Daemon Thread (守护线程) 用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需要,比如垃圾回收线程就是一个守护线程。当 JVM 检测仅剩一个守护线程,而用户线程都已经退出运行时,JVM 就会退出,因为没有如果没有了被守护这,也就没有继续运行程序的必要了。如果有非守护线程仍然存活,JVM 就不会退出。 守护线程并非只有虚拟机内部提供,用户在编写程序时也可以自己设置守护线程。用户可以用 Thread 的 setDaemon(true) 方法设置当前线程为守护线程。 虽然守护线程可能非常有用,但必须小心确保其他所有非守护线程消亡时,不会由于它的终止而产生任何危害。因为你不可能知道在所有的用户线程退出运行前,守护线程是否已经完成了预期的服务任务。一旦所有的用户线程退出了,虚拟机也就退出运行了。 因此,不要在守护线程中执行业务逻辑操作(比如对数据的读写等)。 **另外有几点需要注意:** * setDaemon(true) 必须在调用线程的 start() 方法之前设置,否则会跑出 IllegalThreadStateException 异常。 * 在守护线程中产生的新线程也是守护线程。 * 不要认为所有的应用都可以分配给守护线程来进行服务,比如读写操作或者计算逻辑。 守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。 当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。 main() 属于非守护线程。 使用 setDaemon() 方法将一个线程设置为守护线程。 ~~~java public static void main(String[] args) { Thread thread = new Thread(new MyRunnable()); thread.setDaemon(true); } ~~~ ### sleep() Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒。 sleep() 可能会抛出 InterruptedException,因为异常不能跨线程传播回 main() 中,因此必须在本地进行处理。线程中抛出的其它异常也同样需要在本地进行处理。 ~~~java public void run() { try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } ~~~ ### [](https://github.com/frank-lam/fullstack-tutorial/blob/master/notes/JavaArchitecture/03-Java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B.md#yield)yield() 对静态方法 Thread.yield() 的调用声明了**当前线程已经完成了生命周期中最重要的部分**,可以切换给其它线程来执行。该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。 ~~~java public void run() { Thread.yield(); } ~~~ ### 线程阻塞 线程可以阻塞于四种状态: * 当线程执行 Thread.sleep() 时,它一直阻塞到指定的毫秒时间之后,或者阻塞被另一个线程打断; * 当线程碰到一条 wait() 语句时,它会一直阻塞到接到通知 notify()、被中断或经过了指定毫秒时间为止(若制定了超时值的话) * 线程阻塞与不同 I/O 的方式有多种。常见的一种方式是 InputStream 的 read() 方法,该方法一直阻塞到从流中读取一个字节的数据为止,它可以无限阻塞,因此不能指定超时时间; * 线程也可以阻塞等待获取某个对象锁的排他性访问权限(即等待获得 synchronized 语句必须的锁时阻塞)。 > 注意,并非所有的阻塞状态都是可中断的,以上阻塞状态的前两种可以被中断,后两种不会对中断做出反应 <br> ## **中断** 一个线程执行完毕之后会自动结束,如果在运行过程中发生异常也会提前结束。 ### InterruptedException 通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于**阻塞、限期等待或者无限期等待**状态,那么就会抛出 InterruptedException,从而提前结束该线程。但是不能中断 I/O 阻塞和 synchronized 锁阻塞。 对于以下代码,在 main() 中启动一个线程之后再中断它,由于线程中调用了 Thread.sleep() 方法,因此会抛出一个 InterruptedException,从而提前结束线程,不执行之后的语句。 ~~~java public class InterruptExample { private static class MyThread1 extends Thread { @Override public void run() { try { Thread.sleep(2000); System.out.println("Thread run"); } catch (InterruptedException e) { e.printStackTrace(); } } } } ~~~ ~~~java public static void main(String[] args) throws InterruptedException { Thread thread1 = new MyThread1(); thread1.start(); thread1.interrupt(); System.out.println("Main run"); } ~~~ ~~~java Main run java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at InterruptExample.lambda$main$0(InterruptExample.java:5) at InterruptExample$$Lambda$1/713338599.run(Unknown Source) at java.lang.Thread.run(Thread.java:745) ~~~ ### interrupted() 如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。 但是调用 interrupt() 方法会设置线程的中断标记,此时调用 interrupted() 方法会返回 true。因此可以在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。 ~~~java public class InterruptExample { private static class MyThread2 extends Thread { @Override public void run() { while (!interrupted()) { // .. } System.out.println("Thread end"); } } } ~~~ ~~~js public static void main(String[] args) throws InterruptedException { Thread thread2 = new MyThread2(); thread2.start(); thread2.interrupt(); } ~~~ ~~~ Thread end ~~~ ### Executor 的中断操作 调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭,但是如果调用的是 shutdownNow() 方法,则相当于调用每个线程的 interrupt() 方法。 以下使用 Lambda 创建线程,相当于创建了一个匿名内部线程。 ~~~java public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); executorService.execute(() -> { try { Thread.sleep(2000); System.out.println("Thread run"); } catch (InterruptedException e) { e.printStackTrace(); } }); executorService.shutdownNow(); System.out.println("Main run"); } ~~~ ~~~java Main run java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at ExecutorInterruptExample.lambda$main$0(ExecutorInterruptExample.java:9) at ExecutorInterruptExample$$Lambda$1/1160460865.run(Unknown Source) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) at java.lang.Thread.run(Thread.java:745) ~~~ 如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。 ~~~java Future<?> future = executorService.submit(() -> { // .. }); future.cancel(true); ~~~ <br> ## 线程之间的协作 当多个线程可以一起工作去解决某个问题时,如果某些部分必须在其它部分之前完成,那么就需要对线程进行协调。 ### join() 在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。 对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。 ~~~java public class JoinExample { private class A extends Thread { @Override public void run() { System.out.println("A"); } } private class B extends Thread { private A a; B(A a) { this.a = a; } @Override public void run() { try { a.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("B"); } } public void test() { A a = new A(); B b = new B(a); b.start(); a.start(); } } ~~~ ~~~java public static void main(String[] args) { JoinExample example = new JoinExample(); example.test(); } ~~~ ~~~ A B ~~~ ### wait() notify() notifyAll() 调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify()(随机叫醒一个) 或者 notifyAll() (叫醒所有 wait 线程,争夺时间片的线程只有一个)来唤醒挂起的线程。 它们都属于 Object 的一部分,而不属于 Thread。 只能用在**同步方法**或者**同步控制块**中使用!否则会在运行时抛出 IllegalMonitorStateExeception。 使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。 ~~~java public class WaitNotifyExample { public synchronized void before() { System.out.println("before"); notifyAll(); } public synchronized void after() { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("after"); } } ~~~ ~~~java public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); WaitNotifyExample example = new WaitNotifyExample(); executorService.execute(() -> example.after()); executorService.execute(() -> example.before()); } ~~~ ~~~ before after ~~~ ### await() signal() signalAll() java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。 使用 Lock 来获取一个 Condition 对象。 ~~~java public class AwaitSignalExample { private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void before() { lock.lock(); try { System.out.println("before"); condition.signalAll(); } finally { lock.unlock(); } } public void after() { lock.lock(); try { condition.await(); System.out.println("after"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } } ~~~ ~~~java public static void main(String[] args) { ExecutorService executorService = Executors.newCachedThreadPool(); AwaitSignalExample example = new AwaitSignalExample(); executorService.execute(() -> example.after()); executorService.execute(() -> example.before()); } ~~~ ~~~ before after ~~~ ### sleep和wait有什么区别 * sleep 和 wait * wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法; * wait() 会释放锁,sleep() 不会。 * 有什么区别 * sleep() 方法(休眠)是线程类(Thread)的静态方法,调用此方法会让当前线程暂停执行指定的时间,将执行机会(CPU)让给其他线程,但是对象的锁依然保持,因此休眠时间结束后会自动恢复(线程回到就绪状态)。 * wait() 是 Object 类的方法,调用对象的 wait() 方法导致当前线程放弃对象的锁(线程暂停执行),进入对象的等待池(wait pool),只有调用对象的 notify() 方法(或 notifyAll() 方法)时才能唤醒等待池中的线程进入等锁池(lock pool),如果线程重新获得对象的锁就可以进入就绪状态。