多线程
多线程可以充分利用多核的性质去提高效率
线程的栈内存相互独立,每个线程有自己独立的栈内存,有多个栈帧,互不干扰
进程与线程的联系
- 一个进程至少要有一个线程(主线程)
- 线程之间可以进行内存资源(进程提供)共享,可以进行通信
- 每个线程都有自己的运行回路
怎么创建一个线程
-
继承Thread类(无返回值)
- Thread其实也是一个Runnable的实例,通过实例化类后调用start()方法执行线程
-
实现Runnable接口(无返回值)
-
可使用lambda表达式
Runnable r = ()-> System.out.println("running"); Thread thread = new Thread(r); thread.start();
-
必须实现Runnable接口的run()方法,启动线程时先将线程实例化后作为参数传给Thread类,由Thread执行线程,此时会判断目标参数是否有run方法,有则实现目标参数的run,无则使用thread的run方法
-
-
使用Callable和Future
- 当有返回值时,必须实现Callable接口,返回Future对象,通过线程池ExecutorService返回Future对象
-
更推荐使用Runnable接口,可以与线程池等高级API配合
摆脱了Thread继承体系,更灵活(组合优于继承–》组合重用原则)
线程的状态
按照操作系统分5种
-
new 新建状态,当创建一个线程后,没有执行start()方法,所以还处于new新建状态,此时由JVM分配内存,并初始化其变量
-
runnable 就绪状态,执行start()方法后,只是一个就绪状态,能不能运行需要看CPU调度,JVM调用栈和程序计数器,等待调度运行
-
running 运行状态,当就绪的线程分配到CPU,开始执行run()方法,
-
block 阻塞状态,线程因某些原因让出了CPU使用权,让出了CPU时间片,进入等待,直到线程进入就绪状态才有机会获得时间片,转为运行状态
-
等待阻塞
运行状态的线程执行了wait()方法,JVM把该线程放入等待队列中
-
同步阻塞
运行状态的线程A获取对象的同步锁(如synchronized),而该同步锁被线程B占用时,JVM把线程A放到锁池中
-
其他阻塞
运行状态的线程执行了sleep()、join()方法时,或发出IO请求,JVM会把该线程置为阻塞状态
当sleep()状态超时,join()等待线程终止或超时时,IO请求处理完毕时,线程会重新转入就绪状态
-
-
dead 死亡状态,三种方式
- 线程run()或call()方法执行完成,线程正常结束
- 线程抛出未捕获的异常,线程异常结束
- 线程==调用stop()==方法,但是该方法容易导致死锁,不推荐使用
按Java API层面划分六种
BLOCKED , WAITING , TIMED_WAITING
都是 Java API 层面对【阻塞状态】的细分
start() & run()
start()方法执行后线程是为就绪状态,是Thread启动线程的唯一方法,启动线程后无需等待run()方法体执行完毕,可以直接继续执行下面的代码。start()是一个native方法
start()启动后自动调用run()方法,转为运行状态,run()方法运行结束后,线程终止。CPU调度其他线程
start调用run–》异步执行,直接调用run–》同步执行
每个线程对象的start方法只能调用一次,如果调用了多次会出现IllegalThreadStateException
sleep睡眠状态
调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
写在哪个线程中就让哪个线程睡眠
其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
睡眠结束后的线程未必会立刻得到执行
建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
sleep适用于无需锁同步的场景
yield
调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
具体的实现依赖于操作系统的任务调度器
线程优先级
- 会提示调度器优先调度该线程,但是仅仅是一个提示,调度器可以忽略它
- cpu空闲时,设置优先级几乎没作用
yield和优先级不是很容易去控制线程的调度
为了不让while(true)空转浪费cpu,可以使用sleep或yield让出cpu的使用权给其他程序
join
等待线程运行结束
异步
static int r1 = 0;
static int r2 = 0;
public static void main(String[] args) throws InterruptedException {
test2();
}
private static void test2() throws InterruptedException {
Thread t1 = new Thread(() -> {
sleep(1);
r1 = 10;
});
Thread t2 = new Thread(() -> {
sleep(2);
r2 = 20;
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
t1.join(); //等待t1
t2.join(); //等待t2
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
}
将指定的线程t1加入线程,让t2进入等待,直到t1的生命周期结束,此期间t2处于阻塞状态
interrupt打断阻塞
打断 sleep,wait,join 的线程这几个方法都会让线程进入阻塞状态
打断 sleep 的线程, 会清空打断状态,以 sleep 为例
打断正在运行的线程
- isInterrupted()判断是否打断,不会清除标记
- interrupter() 判断是否打断,会清楚标记
不推荐
容易破坏代码块,造成线程死锁,且方法已过时
- stop
- resume
- suspend
以上用wait去替换