线程: 是操作系统能够进行运算调度的最小单位,一个线程指的是进程中一个单一顺序的控制流,每个线程执行不同的任务,一个进程至少包括一个线程
使用多线程的目的
充分利用 CPU 资源,并发做多件事
创建线程的方式
- 自定义类继承 Thread 类重写 run() 方法
- 实现 Runnable 接口,重写 run() 方法,new Thread 时把该类对象当做参数传入
- 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值,通常和
Future 或 FutureTask 联合使用。
Future 是存储 Callable 执行后的结果,使用 Future 获得异步执行结果时,要么调用阻塞方法 get(),要么轮询看 isDone() 是否为 true ,这两种方法都不是很好,因为主线程也会被迫等待。
从 Java 8 开始引入了 CompletableFuture,它针对 Future 做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。
FutureTask 实现了 Runnable 和 Future ,是一个可以取消的异步运算,它有启动和取消运算,查询运算是否完成和取回运算结果等方法,只有当运算完成的时候结果才能返回,如果运算尚未完成,get 方法会被阻塞,由于 FutureTask 也是调用了Runnable 接口所以它可以提交给 Executor 执行。
和 Future 联合使用
public static void main(String[] args) throws Exception {
Callable<String> c = new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "123";
}
};
ExecutorService executorService = Executors.newCachedThreadPool();
Future<String> submit = executorService.submit(c);
// 等 call 方法执行完才会执行下面的代码,和 Thread 不一样
System.out.println("456");
// get 方法会阻塞到 Callable 执行完有返回值之后
System.out.println(submit.get());
executorService.shutdown();
}
和 FutureTask 联合使用
public static void main(String[] args) throws Exception {
Callable<String> c = new Callable<String>() {
@Override
public String call() throws Exception {
Thread.sleep(2000);
return "123";
}
};
FutureTask<String> futureTask = new FutureTask<>(c);
new Thread(futureTask).start();
// 等 call 方法执行完才会执行下面的代码,和 Thread 不一样
System.out.println("456");
// get 方法会阻塞到 Callable 执行完有返回值之后
System.out.println(futureTask.get());
}
CompletableFuture
例子
public class Main {
public static void main(String[] args) throws Exception {
// 创建异步执行任务:
CompletableFuture<Double> cf = CompletableFuture.supplyAsync(Main::fetchPrice);
// 如果执行成功:
cf.thenAccept((result) -> {
System.out.println("price: " + result);
});
// 如果执行异常:
cf.exceptionally((e) -> {
e.printStackTrace();
return null;
});
System.out.println("会先执行我");
// 主线程不要立刻结束,否则CompletableFuture默认使用的线程池会立刻关闭:
Thread.sleep(2000);
}
static Double fetchPrice() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
if (Math.random() < 0.3) {
throw new RuntimeException("fetch price failed!");
}
return 5 + Math.random() * 20;
}
}
CompletableFuture 的优点
- 异步任务结束时,会自动回调某个对象的方法;
- 异步任务出错时,会自动回调某个对象的方法;
- 主线程设置好回调后,不再关心异步任务的执行;
- 多个 CompletableFuture 可以串行或并行;
- 可以实现任意一个 CompletableFuture 或全部 CompletableFuture 执行完毕的后续处理
线程的生命周期
一个线程实例化完成,到这个线程被销毁。
线程的状态
- 新生态:New
一个线程对象被实例化完成,但是还没有做任何操作 - 就绪状态:Ready
一个线程已经被开启,已经开始争抢CPU时间片 - 运行状态:Run
抢到了CPU时间片,开始执行这个线程中的逻辑 - 阻塞状态:Interrupt
一个线程在运行的过程中,受到某些操作的影响,放弃了已经获取到的CPU时间片,并且不再参与CPU时间片的争抢,此时线程处于挂起状态 - 死亡状态:Dead
一个线程对象需要被销毁
等待锁标记的意思就是有一个线程抢到了 cpu 时间片进入运行状态,获取到锁的拥有权,别的线程就进入到锁池状态,等到在运行状态的线程运行结束之后,锁池里面的线程争抢锁标记,谁抢到锁标记谁进入就绪状态。
方法
wait()、await()、notify()、notifyAll()、await()、signal()、signalAll() 必须在获取到锁才能调用,不然会抛出 IllegalMonitorStateException
sleep():使线程由运行状态进入阻塞状态,休眠,不释放锁资源
Thread.currentThread().getName():获取当前运行线程名称
Thread.currentThread().getPriority():获取当前线程的优先级
setPriority():设置优先级
yiled():线程礼让,指的是让当前的运行状态的线程释放自己的CPU资源,由运行状态回到就绪状态,不会释放锁
wait(): 等待唤醒,是Object类中的一个方法,当前线程释放自己的锁标记,并且让出CPU资源,使当前的线程进入等待队列中,只有拿到该资源的线程才能使用此方法,否则抛出 java.lang.IllegalMonitorStateException
notify(): 通知唤醒等待队列中的在该对象上调用wait方法进入等待队列的线程,进入锁池,是Object类中的一个方法
notifyAll(): 唤醒所有在该对象上调用wait方法进入等待队列的线程,并使他们进入锁池,只有获得该对象的锁才能使用在该对象上使用该方法,不会释放锁
setDaemon(true): 把线程设置为守护线程 线程分为守护线程和用户线程,当进程中没有用户进程的时候,JVM会退出
join(): 加入线程,让调用的线程先执行指定时间或执行完毕,再执行主线程,经常用在等待另外一个线程执行结束,在 t1 里调用 t2.join,等待 t2 执行完再执行 t1
interrupt(): 终端调用该方法的线程,如果当前线程由于调用wait(),join()或者sleep()方法被阻塞,则退出阻塞且终端状态,并且抛出InterrruptedException异常
Condition类中的方法
await(): 和wait()一样,用于lock锁
signal(): 和notify一样
这些方法需在有Condition对象的基础上使用
Condition condition = lock.newCondition();
//进入等待队列,也会释放锁,等待唤醒
condition.await();
//唤醒
condition.signal()