一、快速认识多线程
2020-10-01 本文已影响0人
_Colbert
1.1 线程的介绍
对于计算机来说,一个任务就是一个进程,而一个进程最少包含一个线程。每启动一个JVM虚拟机,就会启动一个线程。例如:在使用360安全卫士的时候,可以同时进行病毒查杀和垃圾清理,这就开启了两个不同的线程。
1.2 创建一个线程:
- 创建一个线程,重写run()方法。
- 启动线程,调用start()方法。
public static void main(String[] args) throws InterruptedException {
new Thread(){
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println("听歌");
try {
// 加上sleep让两个线程交替更明显
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}.start();
for (int i = 0; i < 100; i++) {
System.out.println("看视频");
Thread.sleep(100);
}
}
听歌
看视频
听歌
看视频
听歌
看视频
听歌
......
1.3 线程的生命周期
线程的生命周期大概可以分为五个部分:
image.png1.3.1 NEW(新建)
用new关键字创建一个线程而不调用start方法启动,这时候为Thread对象的NEW状态。在没有调用start方法之前,线程是不存在的,所以一般不说是线程的NEW状态。
1.3.2 RUNNABLE(可运行)
调用start方法后,线程才被创建,此时该线程并不一定会立即执行,该线程必须等到CPU的调度才会执行,而等待的这个状态就是RUNNABLE状态。
1.3.3 RUNNING(运行)
当CPU选中该线程时(通过轮询或其他方式),那么此时它才能真正执行自己的代码,此时进入RUNNING状态。
RUNNING转换为其他状态:
- stop( ) ——> TERMINATED。
- sleep( )、wait( ) ——> BLOCKED。
- 进行某个阻塞的IO操作 ——> BLOCKED。
- 获取某个锁资源,加入到阻塞队列 ——> BLOCKED。
- CPU轮询放弃执行该线程 ——> RUNNABLE。
- 线程主动调用yield()方法 ——> RUNNABLE。
1.3.4 BLOCKED(阻塞)
1.3.3中的介绍了进入BLOCKED状态的情况。
BLOCKED进入其他状态:
- stop()、意外死亡 ——> TERMINATED。
- 阻塞结束 ——> RUNNABLE。
- 休眠(sleep)结束 ——> RUNNABLE。
- wait中,其他线程调用notify()/ notifyall ( )唤醒 ——> RUNNABLE。
- 获得的了锁资源 ——> RUNNABLE。
- 阻塞过程被打断(如:其他线程调用interrupt()),——> RUNNABLE。
1.3.5 TERMINATED/DEAD(死亡)
TERMINATED是线程的最终状态,在该状态中不会再切换到其他状态,该状态意味着线程的生命周期结束。
进入TERMINATED状态有以下几种情况:
- 线程正常结束,结束生命周期。
- 线程运行出错,意外结束。
- JVM Crash,导致所有线程结束。
1.4 模板设计模式
从start()方法可以学习模板设计模式:
print类似Thread的start方法,wrapPrint类似run方法。
public class TemplateDemo {
/**
* 父类搭建好流程模板
* @param message
*/
public final void print(String message){
System.out.println("########");
warpPrint(message);
System.out.println("########");
}
/**
* 空的方法,让不同的子类去实现不同的逻辑
* @param message
*/
protected void warpPrint (String message){
}
public static void main(String[] args) {
new TemplateDemo(){
@Override
protected void warpPrint(String message) {
System.out.println(message);
}
}.print("hello");
new TemplateDemo(){
@Override
protected void warpPrint(String message) {
System.out.println(message);
}
}.print("java");
}
########
hello
########
########
java
########
1.5 Runnable接口以及策略模式
很多文章说创建线程有两种方式:一种是继承Thread,另一种是实现Runnable接口。这种说法是不严谨的,只有Thread才是代表线程,而线程里面的逻辑可以由这两种方法来实现。
public class TicketWindowRunnable implements Runnable {
// 没用static修饰,无论用static,还是采用这种Runnable的方式,都有线程安全问题。
private int index = 1;
private final static int MAX = 50;
@Override
public void run() {
while (index < MAX) {
System.out.println(Thread.currentThread() + "的号码是" + (index++));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TestTicketWindow {
public static void main(String[] args) {
final TicketWindowRunnable task = new TicketWindowRunnable();
new Thread(task, "一号窗口").start();
new Thread(task, "二号窗口").start();
new Thread(task, "三号窗口").start();
new Thread(task, "四号窗口").start();
}
}
Thread[一号窗口,5,main]的号码是1
Thread[二号窗口,5,main]的号码是2
Thread[四号窗口,5,main]的号码是3
Thread[三号窗口,5,main]的号码是4
Thread[三号窗口,5,main]的号码是5
Thread[四号窗口,5,main]的号码是6
Thread[二号窗口,5,main]的号码是7
Thread[一号窗口,5,main]的号码是8
note Thread.sleep()更优雅的写法
- Thread.sleep(10)
- Thread.sleep(10*1000);
- Thread.sleep(10601000);
- TimeUnit.MILLISECONDS.sleep(10);
- TimeUnit.SECONDS.sleep(10);
- TimeUnit.MINUTES.sleep(10);