什么是线程:
当操作系统运行一个程序时会为其创建一个进程,一个进程里可以创建多个线程,这些线程都拥有各自的程序计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。处理器在这些线程上高速切换
多线程的好处:
- 提高cpu的利用率
- 提高程序响应速度
线程生命周期
线程的状态
线程实际状态只有下面六个状态、上图中的 Ready 和 Running 是为了方便理解加上的。
New
初始状态:线程刚被创建还未启动
Runnable
运行中:cpu在多个线程中切换执行
- Ready:未争抢到cpu的执行权
- Running:获得cpu的执行权
Blocked
阻塞中:线程未争抢到锁,被加入阻塞队列,当锁被其他线程释放时再去争抢锁
Waiting
等待中:
- wait/park:线程释放了锁,被加入等待队列
- sleep:线程不释放锁,只是放弃cpu的执行权,调度到 Ready 状态
TimeWaiting
超时等待:线程进入等待状态,指定时间后自动唤醒
- wait/park:也可使用 notify、notifyAll、unpark 可唤醒
- sleep:只有等自动唤醒
Terminated
终止:线程执行完成
线程的终止
run 方法中无循环:方法执行完成后自动终止
-
run 方法中存在循环:终止循环即可
可以使用下面标志去终止循环: 1.使用 interrupt 标志 2.使用共享变量 volatile 标志
run 方法中进入了 Waiting/TimeWaiting 状态,可使用 interrupt() 方法中断等待状态,线程捕获 InterruptException 异常
调用 stop 方法,该方法不安全已废弃
线程的中断
线程的 interrupt 的含义并不是将线程终止或者中断暂停的意思,interrupt 真正的含义是线程的一个中断标识,只是一个 Boolean 字段,true 代表线程被中断,线程需要根据这个字段做出相应的操作。
Thread 类中关于中断的方法
//判断当前线程是否被中断
//该方法会清除线程的中断状态,即调用过后会将中断状态改为默认值 false,复位线程
public static boolean interrupted()
//判断当前线程是否被中断
public boolean isInterrupted()
//中断线程,将中断状态设置为true
public void interrupt()
线程对中断状态的处理
Waiting状态:
线程处于等待状态,比如调用了 sleep、wait、join 时,此时调用线程的 interrupt 方法线程会抛出一个 InterruptException ,并且中断状态会由true重置为false,线程被复位到Runable状态。
线程在调用 sleep、wait、join 方法后,会不断的轮询监听 interrupted 标志位,发现其设置为true后,会停止阻塞并抛出 InterruptedException异常。
public static void main(String[] args) throws InterruptedException {
//阻塞状态下调用 interrupt
RunnerC runnerC = new RunnerC();
Thread threadC = new Thread(runnerC);
threadC.start();
TimeUnit.SECONDS.sleep(1);
threadC.interrupt();
}
static class RunnerC implements Runnable{
private int i;
@Override
public void run() {
try {
Thread.sleep(10000);
while(true){
i++;
}
} catch (InterruptedException e) {
System.out.println("InterruptedException throw");
}
//发生异常后 interrupt 状态被重置为 false
System.out.println("interrupt状态:"+Thread.currentThread().isInterrupted());
System.out.println("RunnerC-Count i = "+i);
}
}
Runable塞状态:
线程在正常运行状态下,interrupt 被调用只会将中断状态设置为 true,线程不会做任何操作。这时开发者可以根据中断状态做出相应的处理,比如中断run方法中的循环使得线程自动终止
public static void main(String[] args) throws InterruptedException {
RunnerA runnerA = new RunnerA();
Thread threadA = new Thread(runnerA);
threadA.start();
TimeUnit.SECONDS.sleep(1);
//使用 interrupt 信号终止线程
threadA.interrupt();
}
static class RunnerA implements Runnable{
private int i;
@Override
public void run() {
//根据中断状态来终止循环,从而终止线程
while(!Thread.currentThread().isInterrupted() ){
i++;
}
System.out.println("RunnerA-Count i = "+i);
}
}
也可以使用 volatile 变量进行通信来终止线程
public static void main(String[] args) throws InterruptedException {
RunnerB runnerB = new RunnerB();
Thread threadB = new Thread(runnerB);
threadB.start();
TimeUnit.SECONDS.sleep(1);
//使用共享变量 volatile 终止线程
runnerB.cancle();
}
static class RunnerB implements Runnable{
private int i;
private volatile boolean on = true;
@Override
public void run() {
//根据 volatile 变量来终止循环,从而终止线程
while(on){
i++;
}
System.out.println("RunnerB-Count i = "+i);
}
public void cancle(){
on = false;
}
}
不可中断的操作:
不可中断的操作,包括进入synchronized段以及Lock.lock(),inputSteam.read()等,调用interrupt()对于这几个问题无效,因为它们都不抛出中断异常。如果拿不到资源,它们会无限期阻塞下去。
对于Lock.lock(),可以改用Lock.lockInterruptibly(),可被中断的加锁操作,它可以抛出中断异常。等同于等待时间无限长的Lock.tryLock(long time, TimeUnit unit)。
对于inputStream等资源,有些(实现了interruptibleChannel接口)可以通过close()方法将资源关闭,对应的阻塞也会被放开。
线程间通信
volatile 和 synchronize
通信原理是利用了线程间共享内存,volatile 和 synchronize 都可以保证线程间共享变量的可见性,所以可以通过修改共享变量实现线程间的通信
Wait、notify 实现等待通知机制
wait方法的使用必须在synchronize的范围内,因为调用wait 方法后会释放对象的锁,所以要先获取到锁
wait() 调用该方法线程进入 WAITING 状态,只有等待另外线程的通知或被中断才返回
wait(long timeout),该方法与wait()方法类似,唯一的区别就是在指定时间内,如果没有notify或notifAll方法的唤醒,也会自动唤醒。
notify 通知一个在对象上等待的线程,使其从wait() 方法返回,而返回的前提是该线程获取到对象上的锁
notifAll 通知所有等待在该对象的线程
public class ThreadTest {
static Object lock = new Object();
static boolean flag = true;
public static void main(String[] args) throws InterruptedException {
new Thread(new Wait()).start();
TimeUnit.SECONDS.sleep(1);
new Thread(new Notify()).start();
}
public static class Wait implements Runnable{
@Override
public void run() {
//获取对象锁
synchronized (lock){
//判断是否满足条件,不满足则等待
if (flag){
try {
System.out.println(Thread.currentThread()
+" flag is true. wait @ "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
//调用 wait 方法,该线程会释放对象锁
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//条件满足,执行任务
System.out.println("执行任务----");
}
}
}
public static class Notify implements Runnable{
@Override
public void run() {
//获取对象锁
synchronized (lock){
System.out.println(Thread.currentThread()
+" hold lock notify @ "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
//修改条件
flag = false;
//通知
lock.notifyAll();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
上述例子演示了 wait和notify 的使用,其实原理就是利用synchronize来进行两个线程之间的通信。需要注意的是这里的通信信号就是 flag 变量,因为flag的修改是在synchronize中进行的,所以保证了flag 的可见性。
还有就是调用 wait 方法后线程是处于 WAITING 状态的不是阻塞状态,该线程是不会去争抢cpu资源的,等到唤醒线程释放锁后才会从 wait 方法返回,由等待状态变为阻塞状态
利用 wait/notify 可以实现经典的生产者消费者模式
join
join():当前线程等该加入该线程后面,等待该线程终止。
join(long millis):当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度。
join(long millis,int nanos):等待该线程终止的时间最长为 millis 毫秒 + nanos纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度。
join 是利用 wait方法实现的:
// join JDK 源码
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
Lock 中的等待通知机制
synchronized 使用对象的 wait,notify 方法实现等待通知机制,因为 synchronized 的实现原理就是对象锁。
Lock 使用的不是对象锁,所以无法使用 object的wait,notify方法
Lock 使用 Condition 来实现等待通知机制
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
static boolean flag = true;
public static class Wait implements Runnable{
@Override
public void run() {
//获取对象锁
lock.lock();
try {
//判断是否满足条件,不满足则等待
if (flag){
System.out.println(Thread.currentThread()
+" flag is true. wait @ "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
//调用 wait 方法
condition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
//条件满足,执行任务
System.out.println("执行任务----");
}
}
public static class Notify implements Runnable{
@Override
public void run() {
//获取对象锁
lock.lock();
try {
System.out.println(Thread.currentThread()
+" hold lock notify @ "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
//修改条件
flag = false;
//通知
condition.signalAll();
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}