type
status
date
slug
summary
tags
category
icon
password
example-row
example-row

生命周期

Java线程的生命周期包括以下几个状态:
  • New(新建): 线程对象被创建,但尚未启动。
  • Runnable(可运行): 线程已启动,等待CPU时间片执行。
  • Blocked(阻塞): 线程等待某个监视器锁。
  • Waiting(等待): 线程等待另一个线程执行特定操作。
  • Timed Waiting(计时等待): 线程等待一定时间。
  • Terminated(终止): 线程已完成执行。

优先级

线程优先级分为10个等级,1最低,5默认,10最高。线程提供了3个常量:
  • MIN_PRIORITY:1 对应最低优先级;
  • MAX_PRIORITY: 10 对应最高优先级;
  • NORM_PRIORITY:5 默认优先级。

线程使用

在Java中,线程可以通过继承Thread类或实现RunnableCallable接口来创建。实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。

继承Thread

实现Runnable接口

实现Callable接口

与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。

相关API

获取当前线程

获取当前线程ID

获取当前线程名字

获取当前线程优先级

获取当前线程存活状态

获取当前线程是否为守护线程:

获取当前线程是否被中断:

守护线程

守护线程是程序运行时在后台提供服务的线程,不属于程序中不可或缺的部分。当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。使用setDaemon()方法将一个线程设置为守护线程。
输出结果:

线程阻塞

Thread提供了一个静态方法: sleep,该方法会阻塞运行当前方法的线程指定毫秒。当超时后,线程会自动回到Runnable状态,等待再次分配时间片运行:

线程中断

一个线程执行完毕之后会自动结束,如果在运行过程中发生异常也会提前结束。

InterruptedException

通过调用一个线程的 interrupt() 来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就会抛出 InterruptedException,从而提前结束该线程。该方法无法中断 I/O 阻塞和 synchronized 锁阻塞。
执行main方法后,打印输出:
在 main() 中启动一个线程之后再中断它,由于线程中调用了 Thread.sleep()方法,因此会抛出一个 InterruptedException,从而提前结束线程,不执行之后的语句。

interrupted()

如果一个线程的run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出InterruptedException的操作,那么调用线程的interrupt()方法就无法使线程提前结束。
但是调用interrupt()方法会设置线程的中断标记,此时调用interrupted()方法会返回 true。因此可以在循环体中使用interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。
执行main方法后打印输出:

Executor 的中断操作

调用 Executor 的 shutdown()方法会等待线程都执行完毕之后再关闭,但是如果调用的是shutdownNow()方法,则相当于调用每个线程的interrupt()方法。
执行main方法后打印输出:
如果只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future<?> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。
执行main方法后打印输出:

线程互斥同步

Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。

synchronized对比ReentrantLock

1. 锁的实现
synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。
2. 性能
新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。
3. 等待可中断
当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
ReentrantLock 可中断,而 synchronized 不行。
4. 公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。
synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。
5. 锁绑定多个条件
一个 ReentrantLock 可以同时绑定多个 Condition 对象。

使用选择

除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。

线程间协作

join()

在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
上面示例代码中,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。

等待/通知机制(wait/notify/notifyAll)

这些方法是Object类的一部分,用于线程间的协作。
  • wait(): 使当前线程进入等待状态,直到被其他线程通知(通过notify或notifyAll)。
  • notify(): 唤醒一个正在等待该对象监视器的线程。
  • notifyAll(): 唤醒所有正在等待该对象监视器的线程。

阻塞队列(BlockingQueue)

Java的java.util.concurrent包提供了多种阻塞队列,如ArrayBlockingQueueLinkedBlockingQueue等,可以用来实现线程间的生产者-消费者模型。

信号量(Semaphore)

Semaphore(信号量)是Java并发编程中用于控制同时访问特定资源的线程数量的机制。线程可以通过acquire方法获取许可,通过release方法释放许可。信号量主要有两种类型:
  1. 公平信号量:按照先到先得的顺序分配许可。
  1. 非公平信号量:不保证许可的分配顺序。

CountDownLatch 和 CyclicBarrier

这两个类都用于实现线程的同步,但有不同的使用场景。
  • CountDownLatch:一个或多个线程等待其他线程完成某些操作。
  • CyclicBarrier:使一组线程达到一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程达到屏障时,屏障才会打开,所有被屏障拦截的线程才会继续执行。
CountDownLatch的使用
  1. 创建CountDownLatch并设置计数器值。
  1. 启动多线程并且调用CountDownLatch实例的countDown()方法。
  1. 主线程调用 await() 方法,这样主线程的操作就会在这个方法上阻塞,直到其他线程完成各自的任务,count值为0,停止阻塞,主线程继续执行。
CyclicBarrier的使用
CyclicBarrier类似于CountDownLatch也是个计数器, 不同的是CyclicBarrier数的是调用了CyclicBarrier.await()进入等待的线程数, 当线程数达到了CyclicBarrier初始时规定的数目时,所有进入等待状态的线程被唤醒并继续。CyclicBarrier初始时还可带一个Runnable的参数,此Runnable任务在CyclicBarrier的数目达到后,所有其它线程被唤醒前被执行。
打印输出:
  • CyclicBarrier barrier = new CyclicBarrier(numberOfThreads, new Runnable() { ... });:创建一个 CyclicBarrier,要求 numberOfThreads 个线程在继续执行前在此屏障处等待,并在所有线程到达屏障时执行给定的屏障操作。
  • barrier.await();:使当前线程在屏障处等待,直到所有线程都调用此方法。
  • 当所有线程都到达屏障后,执行屏障操作,然后所有线程继续执行各自的第二部分任务。
注意事项
  1. 屏障破坏:如果某个线程在等待时被中断或超时,屏障会被破坏,所有等待中的线程都会抛出 BrokenBarrierException
  1. 循环使用CyclicBarrier 可以被重复使用,当所有等待线程都被释放后,屏障会自动重置,可以再次使用。

锁和条件变量(ReentrantLock 和 Condition)

java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。

参考:

【java并发001】理论知识【mysql优化002】深分页下慢sql处理
  • Twikoo