下面说的线程状态指的是java虚拟机中的线程状态,并不是线程在操作系统中的状态。
新建
线程创建后还未启动
运行
在java虚拟机中运行,但是在操作系统层面可能在运行,也可能在等待资源调度,具体有没有运行看操作系统的资源调度
阻塞
线程等待获取锁,锁还没有获得
无限期等待
线程需要等待其它线程显示地唤醒。
等待和阻塞的区别在于等待是主动的,而阻塞是被动的(被动等待获取锁),等待是线程自己调用了wait()等方法进入的。
有限期等待
线程无需等待其他线程显示唤醒,在一定时间后会被系统自动唤醒。
调用sleep(),设置了Timeout参数的wait()方法,以及设置了Timeout参数的join()方法线程就进入了有限期等待状态。
结束
线程结束了任务,或者产生异常就进入了结束状态。
线程执行完毕之后会自动结束,如果在运行过程中发生异常会提前结束。
2.1 InterruptedException和interrupt()
通过调用一个线程的interrupt()来中断该线程,如果该线程处于阻塞、限期等待或者无限期等待状态,那么就抛出InterruptedException,从而提前结束该线程。但是不能中断I/O阻塞和sychronized锁阻塞。
如下:在线程中调用了sleep(2000),线程进入了有限期等待状态,如果在线程等待状态下调用线程的interrupt(),这个时候就会抛出InterruptedException,从而提前结束线程,不执行之后的语句
public void run() {
try {
Thread.sleep(2000);
System.out.println("Thread normally end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2.2 interrupted()
如果一个线程的 run() 方法执行一个无限循环,并且没有执行 sleep() 等会抛出 InterruptedException 的操作,那么调用线程的 interrupt() 方法就无法使线程提前结束。
调用 interrupt() 方法虽然在线程没有异常抛出的情况下不会中断线程,但是会设置线程的中断标记,此时再调用 interrupted() 方法会返回 true。在循环体中使用 interrupted() 方法来判断线程是否处于中断状态,从而提前结束线程。
public void run() {
while (!interrupted()) {
// 循环体
}
System.out.println("Thread end");
}
2.3 Executor 的中断操作
Executor 的 线程中断操作可以分为中断所有线程和中断单个线程。
中断所有线程:
调用 Executor 的 shutdown() 方法会等待线程都执行完毕之后再关闭
调用 Executor 的 shutdownNow() 方法,立即中断每个线程,相当于调用每个线程的 interrupt() 方法。
executorService.execute(() -> {
try {
Thread.sleep(2000);
System.out.println("Thread run");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
executorService.shutdownNow();
System.out.println("Main run");
控制台输出:
Main run
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
.....
中断某个指定线程:
只想中断 Executor 中的一个线程,可以通过使用 submit() 方法来提交一个线程,它会返回一个 Future> 对象,通过调用该对象的 cancel(true) 方法就可以中断线程。
Future> future = executorService.submit(() -> {
// 任务内容
});
future.cancel(true);
3.资源的互斥访问
Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。
synchronized
synchronized是一个关键字,作用域如下
1.同步一个代码块
只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。
2.同步一个方法
和同步代码块一样,作用于同一个对象。
3.同步一个类
作用于整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步。
4.同步一个静态方法
作用于整个类。
ReentrantLock
ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁,是一个类。
public class LockExample {
private Lock lock = new ReentrantLock();
public void func() {
lock.lock();
try {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
} finally {
lock.unlock(); // 确保释放锁,从而避免发生死锁。
}
}
}
synchronized和ReentrantLock两者比较
1.synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。synchronized由java虚拟机自动实现锁的获取与释放操作,ReentrantLock 需用户通过代码手动获取和释放锁
2.是否等待可中断
当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
ReentrantLock 可中断,而 synchronized 不行。
3.是否为公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。
synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。
4.锁绑定多个条件
一个 ReentrantLock 可以同时绑定多个 Condition 对象。
总结分析:
等待可中断、公平锁、锁绑定多个条件这些是ReentrantLock 的高级功能,除非需要用到这些功能,不然优先选择synchronized,使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。
当多个线程一起工作去解决某个问题时,如果某些部分必须在其它部分之前完成,那么就需要对线程进行协调。
线程类提供的join()方法
在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
Object类提供的wait() notify() notifyAll()方法
调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。
只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateException
wait() 和 sleep() 的区别:
wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
wait() 会释放锁,sleep() 不会。
JUC类库中的 Condition 类提供的await() signal() signalAll()
java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。
和Object类提供的wait()等方法相比,await()一般配合ReentrantLock使用。
JUC类库中的 CountDownLatch类
用来控制一个或者多个线程等待多个线程。
维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒。
JUC类库中的 CyclicBarrier类
用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。
和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行
CyclicBarrier 和 CountdownLatch 的一个区别是,CyclicBarrier 的计数器通过调用 reset() 方法可以循环使用,所以它才叫做循环屏障。
JUC类库中的 Semaphore类
Semaphore 类似于操作系统中的信号量,可以控制对互斥资源的访问线程数。
java.util.concurrent.BlockingQueue 接口
BlockingQueue 接口有以下阻塞队列的实现:
FIFO 队列 :LinkedBlockingQueue、ArrayBlockingQueue(固定长度)
优先级队列 :PriorityBlockingQueue
接口提供了阻塞的 take() 和 put() 方法:如果队列为空 take() 将阻塞,直到队列中有内容;如果队列为满 put() 将阻塞,直到队列有空闲位置。
BlockingQueue 用来实现生产者消费者问题
ForkJoin
主要用于并行计算中,把大的计算任务拆分成多个小任务并行计算。也就是分治法。
ForkJoin 使用 ForkJoinPool 来启动,它是一个特殊的线程池,线程数量取决于 CPU 核数。
public class ForkJoinPool extends AbstractExecutorService
作为并行计算的任务类继承RecursiveTask,并重写compute()方法,通过调用任务类的fork()方法开启小任务,调用join()方法汇总结果。
比较分析:
多用同步工具类,少用 wait() 和 notify()。CountDownLatch, CyclicBarrier, Semaphore 等同步类简化了编码操作,而用 wait() 和 notify() 实现复杂控制流比较困难;其次,这些同步类是由最好的企业编写和维护。



