1. 基础知识点回顾
1.1 并发和并行
并发(Concurrent):系统只有一个CPU,多个线程在操作时,在一段时间内只有一个线程在运行,其他线程处于挂起状态。
并发编程的本质:充分利用CPU的资源
并行(Parallel):系统有一个以上CPU时,多个线程可以同时执行
* 应用之异步调用
从方法调用的角度:
- 需要等待结果返回,才能继续运动就是同步
- 不需要等待结果返回,就能继续运行就是异步
注意:同步在多线程中,还有另外一层意思,让多个线程步调一致
1) 设计
多线程可以让方法执行变为异步(即不要干巴巴等着),比如读取磁盘文件时,假设读取操作花费了5秒,如果没有线程调度机制,这五秒调用者什么都做不了,其他代码都得暂停
2) 结论
- 比如在项目中视频文件需要转换格式等操作比较费时时,这时开一个新线程处理视频格式转换,避免阻塞主线程
- UI程序中,开线程进行其他操作,避免阻塞UI线程
方法三:FutureTask 配合 Thread
FutureTask 可以接受 Callable类型参数,用来处理有返回结果的情况
//创建任务对象
FutureTask task3 = new FutureTask<>(()->{
log.debug("hello");
return 100;
});
new Thread(task3, "task3").start();
//主线程阻塞,等待task3执行完毕
Integer result = task3.get();
3.4 线程运行原理
栈与栈帧
- 每个栈由多个栈帧组成,对应着每次方法调用所占用的内存
- 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
线程上下文切换
因为以下一些原因导致CPU不再执行当前线程,转而执行另一个线程的代码
- 线程的CPU时间片用完
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了sleep ,yield, wait, join, park, synchronized, lock等方法
当发生上下文切换时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java中对应的概念就是程序计数器,它的作用就是记住下一条jvm指令的执行地址,是线程私有的
- 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量表,操作数栈,返回地址等
- 上下文切换频繁发生会影响性能
3.5 常见方法
方法名 | 功能说明 | 注意 |
---|---|---|
start() | 启动一个新的线程 | start()方法只是让线程进入就绪状态,里面的代码不一定立刻运行(CPU时间片还没分给它)。每个线程对象的start 方法只能调用一次,调用多次会出现IllegalThreadStateException |
run() | 新线程启动后调用的方法 | |
join() | 等待线程运行结束 | 用在两个线程通信时 |
join(long n) | 等待线程运行结束,最多等待n毫秒 | |
getId() | 获取线程id | id唯一 |
getName() | 获取线程名 | |
setName(String) | 修改线程名 | |
getPriority() | 获取线程优先级 | |
setPriority() | 修改线程优先级 | java中规定线程优先级是1~10的整数,较大的优先级能提高该线程被CPU调度的几率 |
getState() | 获取线程状态 | java中线程状态是由6个enum表示,分别为:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING |
isInterrupted() | 判断是否打断 | 不会清除打断标记 |
isAlive() | 线程是否存活(还没有运行完) | |
interrupt() | 打断线程 | 如果被打断线程正在sleep, wait, join 会导致被打断的线程抛出InterruptedException,并清除 打断标记 ;如果打断正在运行的程序,则会设置打断标记 ;park线程被打断,也会设置打断标记 |
interrupted() | 静态方法,判断当前线程是否被打断 | 会清除打断标记 |
sleep(long n) | 静态方法,让当前执行线程休眠n毫秒,休眠时让出CPU时间片给其他线程 | |
yield() | 静态方法,提示线程调度器让出当前线程对CPU的使用 | 主要是为了测试和调试 |
3.7 sleep 与yield
sleep
1.调用sleep 会让当前线程从RUNNING 进入 Timed_Waiting状态
- 其他线程可以使用interrupt 方法打断正在睡眠的线程,这时sleep 方法会抛出InterruptedException.
- 睡眠结束后的线程未必会立刻得到执行
- 建议用TimeUnit 的sleep 代替Thread 的sleep 来获得更好的可读性
yield
- 调用yield 会让当前线程从RUNNING 进入 RUNNABLE 状态,然后调度执行其他同优先级的线程。如果这段时间没有同优先级的线程,那么不能保证让当前线程暂停的效果
- 具体的实现依赖于操作系统的任务调度
2.2 线程优先级
- 线程优先级会提示调度器优先调度该线程,但仅仅是一个提示,调度器可以忽略
- 如果CPU比较忙,那么优先级高的线程就会获得更多的时间片,但CPU闲下来,优先级几乎没用
3.8 join 方法详解
为什么需要join
下面的代码执行,打印的r是什么?
public class JoinTest {
static int r =0;
public static void main(String[] args) {
test();
}
private static void test() {
Thread t = new Thread(()->{
System.out.println(Thread.currentThread().getName() + " start ...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
System.out.println(Thread.currentThread().getName() + " end ...");
});
t.start();
System.out.println(Thread.currentThread().getName() + " result is " + r);
}
}
执行结果:
main result is 0
Thread-0 start ...
Thread-0 end ...
分析:
- 因为主线程和线程t是并行执行的,t线程需要1秒之后才能计算出r=10;
- 而主线程一开始就要打印结果,所以只能打印r = 0;
解决方法
- 让主线程睡一会,用sleep 行不行?为什么?
- 用join,加在 t.start之后即可
public class JoinTest {
static int r =0;
public static void main(String[] args) {
test();
}
private static void test() {
Thread t = new Thread(()->{
System.out.println(Thread.currentThread().getName() + " start ...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
System.out.println(Thread.currentThread().getName() + " end ...");
});
t.start();
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " result is " + r);
}
}
执行结果:
Thread-0 start ...
Thread-0 end ...
main result is 10
join 方法源码
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;
}
}
}
* 应用之同步(案例1)
以调用方角度来讲,如果
- 需要等待结果返回,才能继续运行就是同步
- 不需要等待结果返回,就能继续运行就是异步
public class JoinTest {
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) {
test2();
}
private static void test2() {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r1 = 10;
System.out.println(Thread.currentThread().getName() + " end ...");
});
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r2 = 20;
System.out.println(Thread.currentThread().getName() + " end ...");
});
t1.start();
t2.start();
long start = System.currentTimeMillis();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("r1 is "+ r1 + " ; r2 is " + r2 + " ; time is " + (end - start)); //注意这个时间差值
}
}
注意end - start 的时间差值
执行结果:
Thread-0 start ...
Thread-1 start ...
Thread-0 end ...
Thread-1 end ...
r1 is 10 ; r2 is 20 ; time is 2002
分析如下:
- 第一个join:等待t1 时,t2并没有停止,而在运行
- 第二个join: 1s后,执行到此,t2也运行了 1s,因此只需再等待1s。
如果颠倒两个join 呢?
最终输出都是一样的。
注意 :如果线程提前结束,join 也不必继续等待。
有时效的join(long n)
public class JoinTest {
static int r = 0;
public static void main(String[] args) {
test3();
}
private static void test3() {
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " start ...");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
System.out.println(Thread.currentThread().getName() + " end ...");
});
t.start();
try {
t.join(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " result is " + r);
}
}
执行结果:
Thread-0 start ...
main result is 0
Thread-0 end ...
2.4 interrupt 方法详解
打断 sleep, wait , join 的线程
打断sleep 的线程,会清空打断状态,以sleep为例
public class SleepTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
System.out.println("interrupt is " + t.isInterrupted());
}
}
执行结果:
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.lily.base.SleepTest.lambda0(SleepTest.java:9)
at java.lang.Thread.run(Thread.java:748)
interrupt is false
public class SleepTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
t.interrupt();
System.out.println("interrupt is " + t.isInterrupted()); //还没有睡眠
}
}
执行结果:
interrupt is true(还没有睡眠打断的是正常的线程)
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.lily.base.SleepTest.lambda0(SleepTest.java:9)
at java.lang.Thread.run(Thread.java:748)
打断正常的线程
打断正常运行的线程,不会清空打断标记
public class InterruptTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
while (true) {
}
});
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
调用interrupt()方法打断仍然继续运行,但是此时 打断标记
为真,可根据该字段结束线程
public class InterruptTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
while (true) {
if (Thread.currentThread().isInterrupted()){
break;
}
}
});
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
由此发现打断线程可以实现停止线程。
打断park线程
打断park 线程,不会清除打断标记
public class ParkInterruptTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println("park...");
LockSupport.park();
System.out.println("unpark...");
System.out.println("打断状态 " + Thread.currentThread().isInterrupted());
});
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
执行结果:
park...
unpark...
打断状态 true
如果打断标记已经是true ,则park 会失效。
public class ParkInterruptTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println("park...");
LockSupport.park();
System.out.println("unpark...");
System.out.println("打断状态 " + Thread.currentThread().isInterrupted());
LockSupport.park();
System.out.println("unpark 1...");
});
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.interrupt();
}
}
执行结果:
park...
unpark...
打断状态 true
unpark 1...
2.5 不推荐方法
有一些不推荐使用的方法,方法已过时,容易破坏同步代码块,造成死锁
方法名 | static | 功能说明 |
---|---|---|
stop() | 停止线程运行 | |
suspend() | 挂起(暂停)线程运行 | |
resume | 恢复线程运行 |
3. 主线程和守护线程
默认情况下,java进程需要等待所有 线程都运行结束,才会结束。有一种特殊的线程叫守护线程。只要其他非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束
public class DeamonTest {
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println(Thread.currentThread().getName() + " start");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " end");
});
t.setDaemon(true);
t.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " end");
}
}
执行结果:
Thread-0 start
main end
注意:垃圾回收器就是一种守护线程
4. 线程五种状态
从操作系统层面描述
- 初始状态:仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 可运行状态:指该线程已经被创建(与操作系统线程关联),可以由cpu调度执行
- 运行状态:指获取了CPU时间片运行中的状态
- 当CPU时间片用完,会从
运行状态
转换至可运行状态
,会导致线程上下文切换
- 当CPU时间片用完,会从
- 阻塞状态
- 如果调用了阻塞API,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入
阻塞状态
- 等BIO操作完,会由操作系统唤醒阻塞的线程,转换至
可运行状态
- 如果调用了阻塞API,如BIO读写文件,这时该线程实际不会用到CPU,会导致线程上下文切换,进入
- 终止状态:表示线程已经执行完毕,生命周期已经结束,不会再转换为其他状态
5. 线程六种状态
这时从JAVA api层面描述的
根据Thread.State 枚举,分为六种状态
源码:
public enum State {
NEW,
/**
* 可运行状态。线程对象调用start()方法
* 如果处于Runnable的线程调用yield()让出JVM资源,那么就会进入New状态和其他New状态
* 线程进行竞争重新进入Runnable
*/
RUNNABLE,
/**
* 阻塞状态。阻塞线程不释放当前占有的系统资源,
* 当sleep()结束或者join()等待其他线程来到,当前线程进入Runnable状态等待JVM分配资
* 源。
* 线程对象调用sleep()或者join()方法
*/
BLOCKED,
/**
* 等待状态。
*
* {@link Object#wait() Object.wait} with no timeout
* {@link #join() Thread.join} with no timeout
* {@link LockSupport#park() LockSupport.park}
*
*一个线程已经调用了Object.wait(),会等待另一个线程调用Object.notify()或Object.notify()
*一个已经调用Thread.join()的线程将等待这个指定线程终止
*/
WAITING,
/**
* 定时等待状态:
* 当线程进入Runnable状态,但还没有开始运行的时候,此时发现需要的资源处于同步状态
* synchronized,这个时候线程将会进入Timed_waiting,JVM会使用队列对这些线程进行控
* 制,是这样状态的线程会先得到JVM资源进行执行进入Waiting
* {@link #sleep Thread.sleep}
* {@link Object#wait(long) Object.wait} with timeout
* {@link #join(long) Thread.join} with timeout
* {@link LockSupport#parkNanos LockSupport.parkNanos}
* {@link LockSupport#parkUntil LockSupport.parkUntil}
*
*/
TIMED_WAITING,
TERMINATED;
}
- NEW : 线程刚被创建,但是还没有调用start()方法
- RUNNABLE: 当调用了start() 方法之后,注意,java API层面的RUNNABLE 状态涵盖了操作系统层面的
可运行状态
、运行状态
和阻塞状态
(由于BIO导致的线程阻塞,在Java中无法区分,仍然认为是可运行的) - BLOCKED : WAITING TIMED_WAITING 都是java API层面对
阻塞状态
的细分 - TERMINATED : 当线程代码运行结束
六种状态演示
public class StateTest {
public static void main(String[] args) {
Thread t1 = new Thread(()->{
System.out.println("running");
},"t1");
Thread t2 = new Thread(()->{
while(true){
}
},"t2");
t2.start();
Thread t3 = new Thread(()->{
System.out.println("running");
},"t3");
t3.start();
Thread t4 = new Thread(()->{
synchronized (StateTest.class){
try{
Thread.sleep(100000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
},"t4");
t4.start();
Thread t5 = new Thread(()->{
try{
t2.join();
}catch (InterruptedException e){
e.printStackTrace();
}
},"t5");
t5.start();
Thread t6 = new Thread(()->{
synchronized (StateTest.class){
try{
Thread.sleep(100000);
}catch (InterruptedException e){
e.printStackTrace();
}
}
},"t6");
t6.start();
try{
Thread.sleep(500);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("t1 state is " + t1.getState());
System.out.println("t2 state is " + t2.getState());
System.out.println("t3 state is " + t3.getState());
System.out.println("t4 state is " + t4.getState());
System.out.println("t5 state is " + t5.getState());
System.out.println("t6 state is " + t6.getState());
}
}
执行结果:
running
t1 state is NEW
t2 state is RUNNABLE
t3 state is TERMINATED
t4 state is TIMED_WAITING
t5 state is WAITING
t6 state is BLOCKED