Java并发编程系列文章
《一》多线程基础——Java线程与进程的基本概念《二》多线程基础——Java线程入门类和接口《三》多线程基础——Java线程组和线程优先级《四》多线程基础——Java线程生命周期及转换《五》多线程基础——Java线程间的通信(互斥与协作)《六》实际应用——如何优雅的关闭线程《七》实际应用——生产者与消费者模型
并发编程(多线程)
一直以来都是程序员头疼的难题。曾经听别人总结过并发编程的第一原则,那就是不要写并发程序,哈哈哈。后来发现,这样能够显著提高程序响应和吞吐量的利器,哪还能忍得住不会用呢?
整理出《Java并发编程系列文章》,共计7篇,与君共勉。
《四》多线程基础——Java线程生命周期及转换
- 1、Java线程生命周期
- 1.1、Java线程的6个状态
- (1)NEW(初始化)
- (2)RUNNABLE(可运行或运行中)
- (3)BLOCKED(阻塞)
- (4)WAITING(等待)
- (5)TIMED_WAITING(超时等待)
- (6)TERMINATED(终止)
- 1.2、Java线程状态的转换
- (1)BLOCKED与RUNNABLE状态的转换
- (2)WAITING状态与RUNNABLE状态的转换
- (3)TIMED_WAITING与RUNNABLE状态转换
- (4)线程中断
1、Java线程生命周期
1.1、Java线程的6个状态
// Thread.State 源码
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
(1)NEW(初始化)
private void testStateNew() {
Thread thread = new Thread(() -> {});
System.out.println(thread.getState()); // 输出 NEW
}
只创建了线程⽽并没有调⽤start()
⽅法,此时线程处于NEW
状态。
关于start()的两个引申问题
- 反复调⽤同⼀个线程的start()⽅法是否可⾏?
start()源码中有⼀个threadStatus的变量的判断,如果不等于0,就会直接抛出异常的。因为调用一次start()后,threadStatus值会改变,所以不能。- 假如⼀个线程执⾏完毕(此时处于TERMINATED状态),再次调⽤这个线程的start()⽅法是否可行?
因为处于TERMINATED状态的线程,threadStatus不等于0,所以不能。
(2)RUNNABLE(可运行或运行中)
Java线程的RUNNABLE
状态其实是包括了传统操作系统线程的ready
和running
两个状态的。
(3)BLOCKED(阻塞)
阻塞状态。处于BLOCKED
状态的线程正等待锁的释放以进⼊同步区。
(4)WAITING(等待)
等待状态。处于等待状态的线程变成RUNNABLE
状态需要其他线程唤醒。
调⽤如下3个⽅法会使线程进⼊等待状态:
Object.wait():使当前线程处于等待状态直到另⼀个线程唤醒它;
Thread.join():等待线程执⾏完毕,底层调⽤的是Object实例的wait⽅法;
LockSupport.park():除⾮获得调⽤许可,否则禁⽤当前线程进⾏线程调度。
(5)TIMED_WAITING(超时等待)
超时等待状态。线程等待⼀个具体的时间,时间到后会被⾃动唤醒。
调⽤如下⽅法会使线程进⼊超时等待状态:
Thread.sleep(long millis):使当前线程睡眠指定时间;
Object.wait(long timeout):线程休眠指定时间;
Thread.join(long millis):等待当前线程最多执⾏millis毫秒,如果millis为0,则会⼀直执⾏;
LockSupport.parkNanos(long nanos): 除⾮获得调⽤许可,否则禁⽤当前线程进⾏线程调度指定时间;
LockSupport.parkUntil(long deadline):同上,也是禁⽌线程进⾏调度指定时间;
(6)TERMINATED(终止)
终⽌状态。此时线程已执⾏完毕。
1.2、Java线程状态的转换
(1)BLOCKED与RUNNABLE状态的转换
处于BLOCKED
状态的线程是因为在等待锁的释放。假如这⾥有两个线程a和b,a线程提前获得了锁并且暂未释放锁,此时b就处于BLOCKED
状态。我们先来看⼀个例⼦:
public void blockedTest() {
Thread a = new Thread(new Runnable() {
@Override
public void run() {
testMethod();
}
}, "a");
Thread b = new Thread(new Runnable() {
@Override
public void run() {
testMethod();
}
}, "b");
a.start();
b.start();
System.out.println(a.getName() + ":" + a.getState()); // 输出?
System.out.println(b.getName() + ":" + b.getState()); // 输出?
}
// 同步⽅法争夺锁
private synchronized void testMethod() {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
除了a、b线程,还有⼀个main
线程。线程启动后执⾏run
⽅法还是需要消耗⼀定时间的。不打断点的情况下,上⾯代码中都应该输出RUNNABLE
。
具体来说,main
线程只保证了a,b两个线程调⽤start()
⽅法(转化为RUNNABLE
状态),还没等两个线程真正开始争夺锁,就已经打印此时两个线程的状态(RUNNABLE
)了。
我们把上⾯的改动⼀下:
······
a.start();
Thread.sleep(1000L); // 需要注意这⾥main线程休眠了1000毫秒,⽽testMethod()⾥休眠了
b.start();
System.out.println(a.getName() + ":" + a.getState()); // 输出?
System.out.println(b.getName() + ":" + b.getState()); // 输出?
由于main
线程休眠,所以线程a的run()
⽅法跟着执⾏,线程b再接着执⾏。在线程a执⾏run()
调⽤testMethod()
之后,线程a休眠了2000ms(注意这⾥是没有释放锁的),main
线程休眠完毕,接着b线程执⾏的时候是争夺不到锁的,所以这⾥输出:
a:TIMED_WAITING
b:BLOCKED
(2)WAITING状态与RUNNABLE状态的转换
转换图我们知道有3个⽅法可以使线程从RUNNABLE
状态转为WAITING
状态。我们主要说一下Object.wait()
和Thread.join()
。
Object.wait()
调⽤wait()⽅法前线程必须持有对象的锁。
调⽤wait()⽅法时,会释放当前的锁,直到有其他线程调⽤notify()/notifyAll()⽅法唤醒等待锁的线程。
需要注意的是,其他线程调⽤notify()⽅法只会随机唤醒单个等待锁的线程。调⽤notifyAll()⽅法唤醒所有等待锁的线程之后,也不⼀定会⻢上把时间⽚分给刚才放弃锁的那个线程,具体要看系统的调度。Thread.join()
调⽤join()⽅法不会释放锁,会⼀直等待当前线程执⾏完毕(转换为TERMINATED状态)。
我们把上面的例子改动一下:
······
a.start();
a.join();
b.start();
System.out.println(a.getName() + ":" + a.getState()); // 输出 TERMINATED
System.out.println(b.getName() + ":" + b.getState());
要是没有调⽤join
⽅法,main
线程不管a线程是否执⾏完毕都会继续往下⾛。a线程启动之后⻢上调⽤了join
⽅法,这⾥main线程就会等到a线程执⾏完毕,所以这⾥a线程打印的状态固定是TERMIATED
。
⾄于b线程的状态,有可能打印RUNNABLE
(尚未进⼊同步⽅法),也有可能打印TIMED_WAITING
(进⼊了同步⽅法)。
(3)TIMED_WAITING与RUNNABLE状态转换
TIMED_WAITING
与WAITING
状态类似,只是TIMED_WAITING
状态等待的时间是指定的。
Thread.sleep(long)
使当前线程睡眠指定时间,并不会释放锁。时间到后,线程会重新进⼊RUNNABLE状态。Object.wait(long)
wait(long)⽅法使线程进⼊TIMED_WAITING状态。这⾥的wait(long)⽅法与
⽆参⽅法wait()相同的地⽅是,都可以通过其他线程调⽤notify()或notifyAll() ⽅法来唤醒。
不同的地⽅是,经过指定时间 long之后它会⾃动唤醒,线程会重新进⼊RUNNABLE状态。Thread.join(long)
join(long)使当前线程执⾏指定时间,并且使线程进⼊TIMED_WAITING状态。
(4)线程中断
在某些情况下,我们在线程启动后发现并不需要它继续执⾏下去时,需要中断线程。⽬前在Java⾥还没有安全直接的⽅法来停⽌线程。需要注意,通过中断操作并不能直接终⽌⼀个线程,⽽是通知需要被中断的线程⾃⾏处理。
简单介绍下Thread类⾥提供的关于线程中断的⼏个⽅法:
Thread.interrupt():中断线程。这⾥的中断线程并不会⽴即停⽌线程,⽽是设 置线程的中断状态为true(默认是flase);
Thread.interrupted():测试当前线程是否被中断。线程的中断状态受这个⽅法的影响,意思是调⽤⼀次使线程中断状态设置为true,连续调⽤两次会使得这 个线程的中断状态重新转为false;
Thread.isInterrupted():测试当前线程是否被中断。与上⾯⽅法不同的是调⽤ 这个⽅法并不会影响线程的中断状态。
在线程中断机制⾥,当其他线程通知需要被中断的线程后,线程中断的状态被设置为true,但是具体被要求中断的线程要怎么处理,完全由被中断线程⾃⼰⽽定,可以在合适的实际处理中断请求,也可以完全不处理继续执⾏下去。