Thread中的实例方法

调用Thread中的方法的时候,在线程类中,有两种方式,一定要理解这两种方式的区别

(1)this.XXX()

这种调用方式表示的线程是线程实例本身

(2)Thread.currentThread.XXX()或Thread.XXX()

上面两种写法是一样的意思。这种调用方式表示的线程是正在执行Thread.currentThread.XXX()所在代码块的线程

简单方法

方法名 描述
start() 就是通知”线程规划器”,此线程可以运行了,正在等待CPU调用线程对象得run()方法,产生一个异步执行的效果。
isAlive() 测试线程是否处于活动状态,只要线程启动且没有终止,方法返回的就是true
getId() 在一个Java应用中,有一个long型的全局唯一的线程ID生成器threadSeqNumber,每new出来一个线程都会把这个自增一次,并赋予线程的tid属性,这个是Thread自己做的,用户无法执行一个线程的Id。
getName() 获取线程名,如果指定,那么线程的名字就是我们自己指定的,如果不指定,那么Thread中有一个int型全局唯一的线程初始号生成器threadInitNum,Java先把threadInitNum自增,然后以”Thread-threadInitNum”的方式来命名新生成的线程
stop() 终止正在运行的线程,已过时,不建议使用
  • 为什么stop现在不建议被使用了?

​ 因为使用 stop 方法是很危险的,就象突然关闭计算机电源, 而不是按正常程序关机。在程序中,我们是不能随便 stop 一个线程的,我们无法知道这个线程正运行在什么状态,它可能持有某把锁,强行中断线程可能导致锁不能释放的问题;或者线程可能 在操作数据库,强行中断线程可能导致数据不一致的问题。正由于使用 stop 方法来终止线程可能会产生不可预料的结果,因此并不推荐使用。

稍稍需要注意的方法

线程优先级

getPriority()和setPriority(int newPriority)

在 Thread 类中有一个实例属性和两个实例方法,专用于进行线程优先级相关的操作,与线程 优先级相关的成员属性为:

1
private int priority; //该属性保存一个 Thread 实例的优先级,1-10 之间的值,默认值是5

这两个方法用于获取和设置线程的优先级,优先级高的CPU得到的CPU资源比较多,设置优先级有助于帮”线程规划器”确定下一次选择哪一个线程优先执行。换句话说,两个在等待CPU的线程,优先级高的线程越容易被CU选择执行。—-> 这个不是绝对的,不能太过于依赖这个设置,看下面的例子就能理解了

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
53
54
55
56
57
58
public class PriorityDemo {

public static final int SLEEP_GAP = 1000;

static class PrioritySetThread extends Thread {
static int threadNo = 1;

public PrioritySetThread() {
super("thread-" + threadNo);
threadNo++;
}

public long opportunities = 0;

@Override
public void run() {
for (int i = 0; ; i++) {
opportunities++;
}
}
}

public static void main(String args[]) throws InterruptedException {
PrioritySetThread[] threads = new PrioritySetThread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new PrioritySetThread();
//优先级的设置,从 1-10
threads[i].setPriority(i + 1);
}
for (int i = 0; i < threads.length; i++) {
//启动线程
threads[i].start();
}
//等待线程运行 1s
Thread.sleep(SLEEP_GAP);
for (int i = 0; i < threads.length; i++) {
//停止线程
threads[i].stop();
}
for (int i = 0; i < threads.length; i++) {
System.out.println(threads[i].getName() +
"优先级为:" + threads[i].getPriority() +
",机会值为:" + threads[i].opportunities
);
}
}
}
// 运行结果
thread-1优先级为:1,机会值为:598373125
thread-2优先级为:2,机会值为:633411616
thread-3优先级为:3,机会值为:715661603
thread-4优先级为:4,机会值为:643894812
thread-5优先级为:5,机会值为:771752736
thread-6优先级为:6,机会值为:764895904
thread-7优先级为:7,机会值为:779410974
thread-8优先级为:8,机会值为:780571797
thread-9优先级为:9,机会值为:792711249
thread-10优先级为:10,机会值为:797973867

如果按照理论来预测结果,那么优先级越高的线程,获得的 CPU 时间片就会越多,从而导致opportunities 就会越大。但是示例中10 条线程停下来之后,结果并非如此,分析案例的执行结果,可以看出以下结论:

  • 整体而言,高优先级的线程获得的执行机会更多。在实例中可以看到:优先级高的线程,执行机会就会偏多,整体对比非常明显。

  • 执行机会的获取具有随机性,优先级高的不一定获得机会多。比如:例子中的 thread-4 比 thread-3 优先级高,但是 thread-4 所获得的机会反而偏少。

设置守护进程

Java中有两种线程,一种是用户线程,一种是守护线程。守护线程是一种特殊的线程,在 JVM 属于保姆的地位:只要 JVM 实例中尚存在任何一个用户线程没有结束,守护线程就能执行自己工作;只有当最后一个用户线程结束,守护线程随着 JVM 一同结束工作。

用户线程和用户线程在 JVM 虚拟机进程终止的方向不同:

  • 用户线程和 JVM 进程是主动关系如果全部的用户线程终止,JVM 虚拟机进程也随之终止;

  • 守护线程和 JVM 进程是被动关系如果 JVM 进程终止,所有的守护线程也随之终止。

isDaeMon、setDaemon(boolean on)

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
public class DaemonDemo extends Thread {
private int i = 0;

@Override
public void run() {
try {
while (true) {
i++;
System.out.println("i = " + i);
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static void main(String[] args) {
try {
DaemonDemo mt = new DaemonDemo();
// 调试时可以设置为false,那么这个程序是个死循环,没有退出条件。
// 设置为true,即可主线程结束,线程也结束。
mt.setDaemon(true);
mt.start();
Thread.sleep(5000);
// 理论上打印了 主线程已结束! 以后子线程就不继续输出了,
// 但是子线程并不会被立即停止,可能还会执行一会,就和下面的运行结果一样
System.out.println("主线程已结束!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// 运行结果
i = 1
i = 2
i = 3
i = 4
i = 5
主线程已结束!
i = 6

守护线程有如下特点:

  • 守护线程必须在启动前,将其守护状态设置为 true;启动之后,不能再将用户线程设置 为守护线程。否则,JVM 会抛出一个 InterruptedException 异常
  • 守护线程存在被 JVM 强行终止的风险,所以,在守护线程中尽量不去访问系统资源, 如文件句柄、数据库连接等等。守护线程被强行终止时,可能会引发系统资源操作的不负责任的 中断,从而导致资源不可逆的损坏。
  • 默认情况下,守护线程创建的线程,也是守护线程
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
public class DaemonDemo02 {

static class DaemonThread extends Thread {
@Override
public void run() {
try {
while (true) {
Thread.sleep(500);
System.out.println(Thread.currentThread() + "===>守护线程状态为:" + this.isDaemon());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public static void main(String[] args) throws InterruptedException {

Thread testThread = new Thread(() -> {
for (int i = 0; i < 5; i++) {
DaemonThread daemonThread = new DaemonThread();
// 设置了false,那自然是非守护线程
// daemonThread.setDaemon(false);
daemonThread.start();
}
});

testThread.setDaemon(true);
testThread.start();

// 给子线程一点时间输出,不然可能看不到结果
Thread.sleep(1000);
System.out.println(Thread.currentThread() + "线程结束");
}
}
// 运行结果
Thread[Thread-2,5,main]===>守护线程状态为:true
Thread[Thread-5,5,main]===>守护线程状态为:true
Thread[Thread-1,5,main]===>守护线程状态为:true
Thread[Thread-3,5,main]===>守护线程状态为:true
Thread[Thread-4,5,main]===>守护线程状态为:true
Thread[main,5,main]线程结束

线程中断

interrupt()

在JAVA中,我们使用interrupt()**来进行线程中断,但是,此方法本质不是用来中断一个线程,而是将线程设置为中断状态。**它有两个作用:

  • 如果此线程处于阻塞状态(如调用了 Object.wait 方法),则会立马退出阻塞,并抛出InterruptedException 异常,线程就可以通过捕获 InterruptedException 来做一定的处理,然后让线程退出。更确切的说,如果线程被 Object.wait, Thread.join 和 Thread.sleep 三种方法之一阻塞,此时调用该线程的 interrupt()方法,那么该线程将抛出一个 InterruptedException 中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。

  • 如果线程正处于运行之中,则线程不受任何影响,继续运行,仅仅是线程的中断标记被设置为 true。所以,程序可以在适当的位置,通过调**isInterrupted()**方法来查看自己是否被中断,并做退出操作。

如果线程的 interrupt 方法先被调用,然后线程开始调用阻塞方法进入阻塞状态,InterruptedException 异常依旧会抛出。如果线程捕获 InterruptedException 异常后, 继续调用阻塞方法,将不再触发 InterruptedException 异常。

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
53
54
public class InterruptDemo {
//睡眠时长
public static final int SLEEP_GAP = 5000;

static class SleepThread extends Thread {
static int threadSeqNumber = 1;

public SleepThread() {
super("sleepThread-" + threadSeqNumber);
threadSeqNumber++;
}

@Override
public void run() {
try {
System.out.println(getName() + " 进入睡眠.");
// 线程睡眠一会
Thread.sleep(SLEEP_GAP);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(getName() + " 发生被异常打断.");
return;
}
System.out.println(getName() + " 运行结束.");
}
}

public static void main(String[] args) throws InterruptedException {
Thread thread1 = new SleepThread();
thread1.start();
Thread thread2 = new SleepThread();
thread2.start();
//主线程等待 2 秒
TimeUnit.SECONDS.sleep(2);
thread1.interrupt(); //打断线程 1
thread1.interrupt(); // 再次打断线程 1 不会抛出异常
//主线程等待 5 秒
TimeUnit.SECONDS.sleep(5);
//打断线程 2,此时线程2已经终止,所以不会抛出异常
thread2.interrupt();
//主线程等待 1 秒
TimeUnit.SECONDS.sleep(1);
System.out.println("程序运行结束.");
}
}
// 运行结果
sleepThread-1 进入睡眠.
sleepThread-2 进入睡眠.
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.cheng.base.method.InterruptDemo$SleepThread.run(InterruptDemo.java:31)
sleepThread-1 发生被异常打断.
sleepThread-2 运行结束.
程序运行结束.

从结果可以看到:

  • sleepThread-1 线程在大致睡眠了 2 秒后,被主线程打断(或者中断)。被打断的 sleepThread-1 线程停止睡眠,并捕获到 InterruptedException 受检异常。程序在异常处理时,进行了直接返回,其后面的执行逻辑被跳过。

  • 捕获了 InterruptedException 异常以后,再次调用interrupt()方法,InterruptedException 异常不再被捕获。

  • sleepThread-2 线程在睡眠了 7 秒后,被主线程中断,但是在 sleepThread-2 线程被中断的时候,已经执行结束,所以 thread2.interrupt() 中断操作没有发生实质性的效果。

总结:

Thread.interrupt( )方法并不像 Thread.stop( )方法那样中止一个正在运行的线程,其作用是设置线程的中断状态位(为 true),至于线程是死亡、还是等待新的任务或是继续运行至下一步, 就取决于这个程序本身。线程可以不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为 true)。总之,Thread.interrupt( )方法只是改变中断状态,不会中断一个正在运行的线程。

interrupt()

在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞状态。换句话说,没有被阻塞的线程,调用interrupt()方法是不起作用的。

isInterrupted()

查看线程是否已经中断,但不清除状态标识。这个和interrupt()方法一样。

线程是否停止执行,需要用户程序去监视线程的isInterrupted()状态,并做对应的处理。 下边就来看一下如何使用 isInterrupted()实例方法监视线程的中断状态,如果发现线程被中断,如何做出对应的处理:

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
public class IsInterruptedDemo {

public static void main(String[] args) {
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("线程启动了");
//一直循环
while (true) {
System.out.println(isInterrupted());
sleepMilliSeconds(5000);
//如果线程被中断,退出死循环
if (isInterrupted()) {
System.out.println("线程结束了");
return;
}
}
}
};
thread.start();
//等待 2 秒
sleepMilliSeconds(2000);
thread.interrupt(); //中断线程
//等待 2 秒
sleepMilliSeconds(2000);
thread.interrupt();
}

public static void sleepMilliSeconds(int millisecond) {
LockSupport.parkNanos(millisecond * 1000L * 1000L);
}
}
// 运行结果
线程启动了
false
线程结束了

线程合并

join()

调用 join()方法的语句可以理解为合并点,合并可以理解为:线程 A 需要在合并点等待,一直等线程 B 执行完成,或者等待超时。

在看join()方法之前请可以先看看线程间通信一文,即wait()/notify()/notifyAll()机制已熟练掌握,才能更好的理解下文。

join()方法的作用是等待线程销毁。join()方法反应的是一个很现实的问题,比如main线程的执行时间是1s,子线程的执行时间是10s,但是主线程依赖子线程执行完的结果,此时有两个方案:

  • 像生产者/消费者模型一样,搞一个缓冲区,子线程执行完把数据放在缓冲区中,通知main线程去拿,这样就不会浪费main线程的时间了
  • 使用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
public class JoinDemo extends Thread {

@Override
public void run() {
try {
// 休眠5s
System.out.println("子线程休眠开始");
Thread.sleep(5000);
System.out.println("子线程休眠完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}
}

public static void main(String[] args) throws InterruptedException {
JoinDemo t1 = new JoinDemo();
t1.start();
t1.join();
System.out.println("当前线程继续执行");
}
}
// 运行结果
子线程休眠开始
子线程休眠完毕
当前线程继续执行

从结果看,join()方法会使调用join()方法的线程(t1线程)所在的线程(main线程)无限阻塞,直到调用join()方法的线程销毁为止,此例中main线程就会无限期阻塞直到t1的run()方法执行完毕。

Join()方法有三个重载版本:

1
2
3
4
5
6
7
8
//重载版本 1:此方法会把当前线程变为 TIMED_WAITING,直到被合并线程执行结束。 join()其实和join(long millis)一样,就是调用了join(0)而已
public final void join() throws InterruptedException;

//重载版本 2:此方法会把当前线程变为 TIMED_WAITING,直到被合并线程结束,或者等待被合并线程执行 millis 时间后开始执行。
public final synchronized void join(long millis) throws InterruptedException;

//重载版本 2:此方法会把当前线程变为 TIMED_WAITING,直到被合并线程结束,或者等待被合并线程执行 millis+nanos 时间后开始执行。
public final synchroinzed void join(long millis, int nanos) throws InterruptedException;

使用 join()方法的要点:

  • join()方法是实例方法,需要使用被合并线程的句柄(或者指针、变量)去调用,如 threadb.join() 。 执行threadb.join( ) 这行代码的当前线程为合并线程, 进入 TIMED_WAITING 等待状态,出让 CPU。

  • 如果设置了被合并线程的执行时间 millis(或者 millis+nanos),并不能保证当前线程 一定会在 millis 时间后变为 RUNNABLE。

  • 如果主动方合并线程的在等待时被中断,则会抛出 InterruptedException 受检异常。

  • join()方法的一个重点是要区分出和sleep()方法的区别。join(2000)也是可以的,表示调用join()方法所在的线程最多等待2000ms,两者的区别在于:

sleep(2000)不释放锁,join(2000)释放锁,因为join()方法内部使用的是wait(),因此会释放锁。看一下join(long millis)的源码就知道了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;

if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}

if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

Thread中的静态方法

Thread类中的静态方法表示操作的线程是”正在执行静态方法所在的代码块的线程“。为什么Thread类中的静态方法是为了能对CPU当前正在运行的线程进行操作。

获取当前线程

currentThread()

currentThread()方法返回的是对当前正在执行线程对象的引用

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
class MyThread04 extends Thread {
static {
System.out.println("静态块的打印:" +
Thread.currentThread().getName());
}

public MyThread04() {
System.out.println("构造方法的打印:" +
Thread.currentThread().getName());
}

@Override
public void run() {
System.out.println("run()方法的打印:" +
Thread.currentThread().getName());
}
}

public class MyThread01 extends Thread{
public static void main(String[] args) {
MyThread04 mt = new MyThread04();
mt.start();
}
}

// 运行结果
静态块的打印:main
构造方法的打印:main
run()方法的打印:Thread-0

这个例子说明了,线程类的构造方法、静态块是被main线程调用的,而线程类的run()方法才是应用线程自己调用的。在这个例子的基础上,再深入:

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
class MyThread05 extends Thread {
public MyThread05() {
System.out.println("MyThread5----->Begin");
System.out.println("Thread.currentThread().getName()----->" +
Thread.currentThread().getName());
System.out.println("this.getName()----->" + this.getName());
System.out.println("MyThread5----->end");
}

@Override
public void run() {
System.out.println("run----->Begin");
System.out.println("Thread.currentThread().getName()----->" +
Thread.currentThread().getName());
System.out.println("this.getName()----->" + this.getName());
System.out.println("run----->end");
}
}

public class MyThread01 extends Thread {
public static void main(String[] args) {
MyThread05 thread05 = new MyThread05();
thread05.start();
}
}

// 运行结果
MyThread5----->Begin
Thread.currentThread().getName()----->main
this.getName()----->Thread-0
MyThread5----->end
run----->Begin
Thread.currentThread().getName()----->Thread-0
this.getName()----->Thread-0
run----->end

就是”this.XXX()”和”Thread.currentThread().XXX()”的区别,这个就是最好的例子。必须要清楚的一点就是:当前执行的Thread未必就是Thread本身

线程休眠

sleep(long millis)

sleep(long millis)方法的作用是在指定的毫秒内让当前”正在执行的线程“休眠(暂停执行)。这个”正在执行的线程“是关键,指的是Thread.currentThread()返回的线程。根据JDK API的说法,”该线程不丢失任何监视器的所属权”,简单说就是sleep代码上下文如果被加锁了,锁依然在,但是CPU资源会让出给其他线程。看一下例子:

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
class MyThread06 extends Thread {
@Override
public void run() {
try {
System.out.println("run threadName = " +
this.getName() + " begin");
Thread.sleep(2000);
System.out.println("run threadName = " +
this.getName() + " end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

public class MyThread01 extends Thread {
public static void main(String[] args) {
MyThread06 mt = new MyThread06();
System.out.println("begin = " + System.currentTimeMillis());
mt.start();
System.out.println("end = " + System.currentTimeMillis());
}
}

// 运行结果
begin = 1604373745809
end = 1604373745810
run threadName = Thread-0 begin
run threadName = Thread-0 end

yield()

暂停当前执行的线程对象,并执行其他线程。这个暂停是会放弃CPU资源的,并且放弃CPU的时间不确定,有可能刚放弃,就获得CPU资源了,也有可能放弃好一会儿,才会被CPU执行。看一下例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class MyThread07 extends Thread {
@Override
public void run() {
long beginTime = System.currentTimeMillis();
int count = 0;
for (int i = 0; i < 500000000; i++) {
Thread.yield();
count = count + i + 1;
}
long endTime = System.currentTimeMillis();
System.out.println("用时:" + (endTime - beginTime) + "毫秒!");
}
}

public class MyThread01 extends Thread {
public static void main(String[] args) {
MyThread07 mt = new MyThread07();
mt.start();
}
}

// 这个可以自己运行看看 每次的结果可能都不一样,证明了yield()方法放弃CPU的时间并不确定

interrupted()

测试当前线程是否已经中断,执行后具有将状态标识清除为false的功能。换句话说,如果连续两次调用该方法,那么返回的必定是false:

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
Thread.currentThread().interrupt();
System.out.println("是否停止1?" + Thread.interrupted());
System.out.println("是否停止2?" + Thread.interrupted());
System.out.println("end!");
}
// 运行结果
是否停止1true
是否停止2false
end!