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
类或实现Runnable
、Callable
接口来创建。实现 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
包提供了多种阻塞队列,如ArrayBlockingQueue
、LinkedBlockingQueue
等,可以用来实现线程间的生产者-消费者模型。信号量(Semaphore)
Semaphore
(信号量)是Java并发编程中用于控制同时访问特定资源的线程数量的机制。线程可以通过acquire
方法获取许可,通过release
方法释放许可。信号量主要有两种类型:- 公平信号量:按照先到先得的顺序分配许可。
- 非公平信号量:不保证许可的分配顺序。
CountDownLatch 和 CyclicBarrier
这两个类都用于实现线程的同步,但有不同的使用场景。
CountDownLatch
:一个或多个线程等待其他线程完成某些操作。
CyclicBarrier
:使一组线程达到一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程达到屏障时,屏障才会打开,所有被屏障拦截的线程才会继续执行。
CountDownLatch的使用
- 创建CountDownLatch并设置计数器值。
- 启动多线程并且调用CountDownLatch实例的countDown()方法。
- 主线程调用
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();
:使当前线程在屏障处等待,直到所有线程都调用此方法。
- 当所有线程都到达屏障后,执行屏障操作,然后所有线程继续执行各自的第二部分任务。
注意事项
- 屏障破坏:如果某个线程在等待时被中断或超时,屏障会被破坏,所有等待中的线程都会抛出
BrokenBarrierException
。
- 循环使用:
CyclicBarrier
可以被重复使用,当所有等待线程都被释放后,屏障会自动重置,可以再次使用。
锁和条件变量(ReentrantLock 和 Condition)
java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。
参考:
- 作者:黄x黄
- 链接:https://hxhowl.site/article/java-concurrent002
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章