热衷学习,热衷生活!😄

沉淀、分享、成长,让自己和他人都能有所收获!😄

一、Thread状态

Java的线程状态描述在Thread类里面的枚举类State中,包路径为java.lang.Thread.State,总共包含以下六种状态:NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public enum State {
// 尚未启动的线程的线程状态
NEW,

// 可运行线程的线程状态,是可运行的线程
// 这个状态在Java虚拟机中进行,但它可能等待来自操作系统的其他资源,比如处理器
RUNNABLE,

// 线程阻塞等待监视器锁的线程状态
// 处于阻塞状态的线程正在等待监视器锁
// 例如synchronized锁
BLOCKED,

// 等待线程的线程状态
// 线程调用以下方法会处于等待状态:Object.wait()不超时、Thread.join()不超时、LockSupport.park()
// 一个处于等待状态的线程正在等待另一个线程执行特定动作,例如:
// 一个线程调用了Object.wait()方法在一个对象上正在等待另一个线程调用Object.nofify()或者
// Object.nofifyAll()方法开启那个对象
// 一个调用了Thread.join()方法的线程正在等待指定线程终止
WAITING,

// 具有指定等待时间的等待线程的线程状态,调用一下方法会处于这个状态:
// Object.wait() 超时、Thread.join()超时
// LockSupport.parkNanos()、LockSupport.parkUntil()
TIMED_WAITING,

// 已终止线程的线程状态
// 线程已执行完毕或者发生异常终止
TERMINATED
}

上面的六种状态相当于描述了一个线程的生命周期,这些状态之间是可以流转的,那么线程的状态是如何流转的呢,我们看下图:

  • NEW:未启动状态。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static void main(String[] args) {
    Thread t = new Thread(){
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName());
    }
    };
    // 这个时候只是new了一个线程,并没有调用start()方法
    // 输出 NEW
    System.out.println(t.getState().name());
    }
  • RUNNABLE:可运行状态,处于可运行状态的线程正在Java虚拟机中运行,但也可能是正在等待来自操作系统资源,比如CPU。在RUNNABLE状态中包含了RUNNINGREADY两个状态,这两个状态是可以互相流转的。当线程调用start()方法后,线程就处于READY状态,等待操作系统分配CPU时间片,分配后进入RUNNING状态。当调用yield()方法后,只是谦让的允许当前线程让出CPU,但是不一定让,由操作系统决定,如果让了当前线程就会进入READY状态,等待系统分配CPU时间片再次进入RUNNING状态。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public static void main(String[] args) {
    Thread t = new Thread(){
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName());
    }
    };
    t.start();
    // 调用start()方法
    // 输出RUNNABLE
    System.out.println(t.getState().name());
    }
  • BLOCKED:阻塞状态。当发生线程锁竞争状态下,没有获取到锁的线程会被挂起进入阻塞状态,比如synchronized锁。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public static void main(String[] args) throws InterruptedException {

    final Object lock = new Object();

    Thread t = new Thread(){
    @Override
    public void run() {
    synchronized (lock){
    System.out.println(Thread.currentThread().getName());
    }
    }
    };
    // main线程先获取锁, t线程start的时候进入阻塞状态
    synchronized (lock) {
    t.start();
    Thread.sleep(1000);
    // 输出BLOCKED
    System.out.println(t.getState().name());
    }
    }
  • WIITING:等待状态,可被唤醒的等待状态,此时线程不会执行也不会被调度,:可被唤醒的等待状态,此时线程不会被执行也不会被系统调度,此状态可以通过 synchronized 获得锁,调用 wait 方法进入等待状态,最后通过Object.notify()Object.nofifyAll()唤醒。下列方法可以让线程进入WAITING状态:Object.wait()Thread.join()LockSupport.park(),调用以下方法可以唤醒登台的线程进入RUNNABLE状态:Object.notify()Object.nofifyAll()LockSupport.unpark(Thread)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public static void main(String[] args) throws Exception {
    final Object lock = new Object();
    Thread t = new Thread(){
    @Override
    public void run(){
    try {
    synchronized (lock) {
    lock.wait();
    System.out.println(Thread.currentThread().getName());
    }
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    }
    };
    t.start();
    Thread.sleep(1000);
    synchronized (lock) {
    // 输出WAITING
    System.out.println(t.getState().name());
    lock.notify();
    }
    }
  • TIMED_WAITING:具有等待时间的等待状态,此时线程不会执行也不会被调度,直到等待时间到期后才会被执行。下列方法可以让线程进入TIMED_WAITING状态:Thread.sleep(long)Object.wait(long)Thread.join(long)LockSupport.parkNanos()LockSupport.parkUntil()。除了等待时间到期会被执行外,还可以调用以下方法:Object.notify()Object.nofifyAll()LockSupport.unpark(Thread)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public static void main(String[] args) throws Exception {
    Thread t = new Thread(() ->{
    try {
    Thread.sleep(10000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    });
    t.start();
    Thread.sleep(1000);
    // 输出TIMED_WAITING
    System.out.println(t.getState().name());
    }
  • TERMINATED:已终止状态,线程已完成执行或者发生了异常终止。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static void main(String[] args) {
    Thread t = new Thread(){
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName());
    }
    };
    t.start();
    // 输出 TERMINATED
    System.out.println(t.getState().name());
    }

二、创建Thread方式

创建线程的方式有三种,分别是:继承Thread类、实现Runnable接口、实现Callable接口和使用Future

继承Thread

1
2
3
4
5
6
public class MyThread extends Thread {
@Override
public void run() {
// do something
}
}

实现Runnable

1
2
3
4
5
6
public class MyThread implements Runnable {
@Override
public void run() {
// do something
}
}

实现Callable接口和使用Future

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ThreadDemo implements Callable {

@Override
public String call() {
return "str";
}

public static void main(String[] args) throws Exception {
ThreadDemo threadDemo = new ThreadDemo();
FutureTask<String> future = new FutureTask<String>(threadDemo);
Thread thread = new Thread(future);
thread.start();
System.out.println(future.get());
}
}

三、Thread方法使用

Thread类常用的方法有以下:

  • start():启动当前线程。
  • run():线程启动的时候执行的方法。
  • currentThread():静态方法,返回执行当前代码的线程。
  • getName():获取当前线程的名字。
  • setName(String name):设置当前线程的名字
  • yield():释放当前CPU的执行权(但也有可能下一刻的执行权又回到了当前线程,主控权还是在CPU手上)。
  • join():当线程a调用线程b的join()方法,此时线程a进入阻塞状态,直到线程b完全执行完之后,线程a结束阻塞状态。
  • stop:当执行此方法时,强制结束当前线程(已停用)。
  • sleep():让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前进程是阻塞状态。
  • getPriority():获取线程优先级。
  • isAlive():判断当前线程是否存活(线程执行完之前都是存活的)。
  • setDeamon():设置守护线程。
  • isDaemon():是否是守护线程。
  • isInterrupted():线程是否中断。

守护线程

守护线程又称后台线程,默认创建出来的线程都是前台线程,可以通过setDeamon(true)设定为后台线程。

后台线程和前台线程主要区分在结束时机:当一个进程中的所有前台线程都执行完毕,无论进程中的后台线程是否执行完毕,都要强制中断。

看以下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void main(String[] args) {
Thread rose = new Thread(() ->{
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Rose:啊啊啊啊啊AAAAAaaaaa......");
System.out.println("效果:噗通......");
}
});

Thread jack = new Thread(() ->{
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("Jack:You jump,I jump");
}
});

// jack线程设置为后台线程, 一定要在start()方法之前调用
jack.setDaemon(true);

rose.start();;
jack.start();

//while(true);//有进程没结束,后台线程不会结束。
}

上面代码输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Jack:You jump,I jump
Rose:Let me go die!
Rose:Let me go die!
Jack:You jump,I jump
Jack:You jump,I jump
Rose:Let me go die!
Jack:You jump,I jump
Rose:Let me go die!
Rose:Let me go die!
Jack:You jump,I jump
Jack:You jump,I jump
Rose:Let me go die!
Jack:You jump,I jump
Rose:Let me go die!
Jack:You jump,I jump
Rose:Let me go die!
Rose:Let me go die!
Jack:You jump,I jump
Jack:You jump,I jump
Rose:Let me go die!
Rose:啊啊啊啊啊AAAAAaaaaa......
效果:噗通......

从输出可以看出前台线程执行完毕之后,后台线程就立即停止了,如果把while(true)去掉注释,那么后台线程会一直执行。

sleep

sleep()方法可以使当前方法的线程阻塞指定毫秒,超时后,该线程方法会自动回到RUNNABLE状态等待再次分配时间片执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
System.out.println("程序开始了");
try{
/*
* 这里会使main线程阻塞5秒,在此过程中
* 若其他线程调用了main线程的interrupt方法
* 试图中断main线程时,sleep方法会抛出异常
*/
Thread.sleep(5000);
}catch(InterruptedException e){

}
System.out.println("程序结束了");
}

join

join()方法会阻塞调用当前方法的线程,并在join()方法所属对象线程上等待,直到该线程执行完毕才会解除阻塞继续执行。

举个栗子:a线程调用b线程的join()方法,那么a线程就会进入阻塞状态,直到b线程执行完毕才会解除阻塞继续往下执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void main(String[] args) {

// 下载图片线程
final Thread download = new Thread(() ->{
System.out.println("download:开始下载照片");
for (int i = 1; i <= 100; i++) {
System.out.println("下载进度:"+i+"%");
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("download:图片下载完毕");
});

// 显示图片线程
final Thread show = new Thread(() ->{
System.out.println("show:开始显示图片");
try {
// 只有图片下载完毕之后才能显示图片
// 调用下载图片线程的join()方法阻塞显示图片线程直到图片下载完毕
download.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("show:图片显示完毕");
});
download.start();
show.start();
}

上面代码输出如下:

1
2
3
4
5
6
7
show:开始显示图片
download:开始下载照片
下载进度:1%
下载进度:...
下载进度:100%
download:图片下载完毕
show:图片显示完毕

wait & nofify

wait()notify()方法是在Object类定义的,用来协调线程同步使用,相比join()同步的及时性更强,因为join()方法必须等待另外一个线程的所有工作都结束。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public static boolean isFinish;
public static Object obj = new Object();
public static void main(String[] args) {
// 下载图片线程
final Thread download = new Thread(() ->{
System.out.println("download:开始下载照片");
for (int i = 1; i <= 100; i++) {
System.out.println("down图片进度:"+i+"%");
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("download:图片下载完毕");
isFinish = true;
// 图片下载完毕就可以通知显示图片线程显示图片, 没必要等下载附件执行完毕
synchronized (obj) {
obj.notify();
}

// 开始下载附件
System.out.println("down:开始下载附件...");
for (int i = 1; i <= 100; i++) {
System.out.println("down附件进度:" + i + "%");
try {
Thread.sleep(50);
} catch (InterruptedException e) {
}
}
System.out.println("down:附件下载完毕!");
});

// 显示图片线程
final Thread show = new Thread(() ->{
System.out.println("show:开始显示图片");
try {
// obj.wait()方法会导致show线程进入阻塞状态直到obj调用notify()方法
synchronized (obj) {
obj.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!isFinish) {
throw new RuntimeException("图片不存在!");
}
System.out.println("show:图片显示完毕");
});
download.start();
show.start();
}

上面输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
show:开始显示图片
download:开始下载照片
down图片进度:1%
down图片进度:...
down图片进度:100%
download:图片下载完毕
down:开始下载附件...
show:图片显示完毕
down附件进度:1%
down附件进度:...
down附件进度:100%
down:附件下载完毕!

从上面输出我们看出当下载线程图片下载完毕之后,显示图片线程就执行了,不需要等待附件下载完毕再执行。

Oject还有一个notifyAll()方法,当该方法被调用时,等待队列中的所有对象都被会唤醒。

yield

yield()方法会使线程让出CPU,但不是暂停执行,线程还会继续进行CPU的争夺,但是也不是一定就会让出,这个由操作系统决定。如果一个线程不那么重要,或者对应任务的优先级非常低,或者不希望它占用过多的资源,那么就可以使用yield()方法给予其它线程更多的执行机会。

sleep和wait的区别

  • wait()方法阻塞的线程可以通过notify()或者notifyAll()方法唤醒,而执行sleep(0方法的线程只能一直休眠到指定时间,不可被唤醒。
  • 当被wait()方法阻塞的线程被唤醒的时候,会释放目标对象的锁,而sleep()方法指定的睡眠时候到的时候不会释放任何资源。