什么是线程?
- 线程(thread)是一个程序内部的一条执行路径。
- main方法的执行其实就是一条单独的执行路径。
- 程序中如果只有一条执行路径,那么这个程序就是单线程的程序。
多线程的创建
- 1、 Thread类
- Java是通过java.lang.Thread 类来代表线程的。
- 按照面向对象的思想,Thread类应该提供了实现多线程的方式。
public class Main {
public static void main(String[] args) {
Thread t = new MyThread();
t.start(); // 启动新线程
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println("start new thread!");
}
}
2、实现Runnable接口
多线程的状态
- 创建(new)状态: 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存
- 就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
- 运行(running)状态: 执行run()方法
- 阻塞(blocked)状态: 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用
- 死亡(terminated)状态: 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)
当线程启动后,它可以在`Runnable`、`Blocked`、`Waiting`和`Timed Waiting`这几个状态之间切换,直到最后变成`Terminated`状态,线程终止。
守护线程
Java程序入口就是由JVM启动`main`线程,`main`线程又可以启动其他线程。当所有线程都运行结束时,JVM退出,进程结束。如果有一个线程没有退出,JVM进程就不会退出。
Thread t = new MyThread();
t.setDaemon(true);
t.start();
线程同步
线程同步的核心思想
把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来。
synchronized(同步锁对象) {
操作共享资源的代码(核心代码)
}
死锁
一个线程可以获取一个锁后,再继续获取另一个锁。例如:
public void add(int m) {
synchronized(lockA) { // 获得lockA的锁
this.value += m;
synchronized(lockB) { // 获得lockB的锁
this.another += m;
} // 释放lockB的锁
} // 释放lockA的锁
}
public void dec(int m) {
synchronized(lockB) { // 获得lockB的锁
this.another -= m;
synchronized(lockA) { // 获得lockA的锁
this.value -= m;
} // 释放lockA的锁
} // 释放lockB的锁
}
在获取多个锁的时候,不同线程获取多个不同对象的锁可能导致死锁。
那么我们应该如何避免死锁呢?答案是:线程获取锁的顺序要一致。即严格按照先获取`lockA`,再获取`lockB`的顺序,改写`dec()`方法如下:
public void dec(int m) {
synchronized(lockA) { // 获得lockA的锁
this.value -= m;
synchronized(lockB) { // 获得lockB的锁
this.another -= m;
} // 释放lockB的锁
} // 释放lockA的锁
}
线程池
不使用线程池的问题
ExecutorService的常用方法
void execute(Runnable command) | 执行任务/命令,没有返回值,一般用来执行 Runnable 任务 |
Future<T> submit(Callable<T> task) | 执行任务,返回未来任务对象获取线程结果,一般拿来执行 Callable 任务 |
void shutdown() | 等任务执行完毕后关闭线程池 |
List<Runnable> shutdownNow() | 立刻关闭,停止正在执行的任务,并返回队列中未执行的任务 |