线程的创建和启动

通过创建Thread对象来创建一个新的线程,Thread构造方法中需要传入一个Runnable接口的实现(其实就是编写要在另一个线程执行的内容逻辑)同时Runnable只有一个未实现方法,因此可以直接使用lambda表达式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50; i++) {
System.out.println("我是一号线程:"+i);
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50; i++) {
System.out.println("我是二号线程:"+i);
}
});
t1.start();
t2.start();
}

虽然stop()方法能够终止此线程,但是并不是所推荐的做法,有关线程中断相关问题,我们会在后面继续了解。

线程的休眠和中断

我们前面提到,一个线程处于运行状态下,线程的下一个状态会出现以下情况:

  • 当CPU给予的运行时间结束时,会从运行状态回到就绪(可运行)状态,等待下一次获得CPU资源。
  • 当线程进入休眠 / 阻塞(如等待IO请求) / 手动调用wait()方法时,会使得线程处于等待状态,当等待状态结束后会回到就绪状态。
  • 当线程出现异常或错误 / 被stop() 方法强行停止 / 所有代码执行结束时,会使得线程的运行终止。

而这个部分我们着重了解一下线程的休眠和中断,首先我们来了解一下如何使得线程进如休眠状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) {
Thread t = new Thread(() -> {
try {
Thread.sleep(10000); //休眠10秒
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t.start();
try {
Thread.sleep(3000); //休眠3秒,一定比线程t先醒来
t.interrupt(); //调用t的interrupt方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}

我们发现,每一个Thread对象中,都有一个interrupt()方法,调用此方法后,会给指定线程添加一个中断标记以告知线程需要立即停止运行或是进行其他操作,由线程来响应此中断并进行相应的处理,我们前面提到的stop()方法是强制终止线程,这样的做法虽然简单粗暴,但是很有可能导致资源不能完全释放,而类似这样的发送通知来告知线程需要中断,让线程自行处理后续,会更加合理一些,也是更加推荐的做法。我们来看看interrupt的用法:

通过isInterrupted()可以判断线程是否存在中断标志,如果存在,说明外部希望当前线程立即停止,也有可能是给当前线程发送一个其他的信号,如果我们并不是希望收到中断信号就是结束程序,而是通知程序做其他事情,我们可以在收到中断信号后,复位中断标记,然后继续做我们的事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("线程开始运行!");
while (true){ //无限循环
if(Thread.currentThread().isInterrupted()){ //判断是否存在中断标志
break; //响应中断
}
}
System.out.println("线程被中断了!");
});
t.start();
try {
Thread.sleep(3000); //休眠3秒,一定比线程t先醒来
t.interrupt(); //调用t的interrupt方法
} catch (InterruptedException e) {
e.printStackTrace();
}
}

线程的优先级

实际上,Java程序中的每个线程并不是平均分配CPU时间的,为了使得线程资源分配更加合理,Java采用的是抢占式调度方式,优先级越高的线程,优先使用CPU资源!我们希望CPU花费更多的时间去处理更重要的任务,而不太重要的任务,则可以先让出一部分资源。线程的优先级一般分为以下三种:

  • MIN_PRIORITY 最低优先级
  • MAX_PRIORITY 最高优先级
  • NOM_PRIORITY 常规优先级

线程的礼让和加入

我们还可以在当前线程的工作不重要时,将CPU资源让位给其他线程,通过使用yield()方法来将当前资源让位给其他同优先级线程:

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
package org.wsy.juc;

public class JUC_04 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("线程1开始运行!");
for (int i = 0; i < 50; i++) {
if(i % 5 == 0) {
System.out.println("让位!");
Thread.yield();
}
System.out.println("1打印:"+i);
}
System.out.println("线程1结束!");
});
Thread t2 = new Thread(() -> {
System.out.println("线程2开始运行!");
for (int i = 0; i < 50; i++) {
System.out.println("2打印:"+i);
}
});
t1.start();
t2.start();
}
}

当我们希望一个线程等待另一个线程执行完成后再继续进行,我们可以使用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
package org.wsy.juc;

public class JUC_05 {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println("线程1开始运行!");
for (int i = 0; i < 50; i++) {
System.out.println("1打印:"+i);
}
System.out.println("线程1结束!");
});
Thread t2 = new Thread(() -> {
System.out.println("线程2开始运行!");
for (int i = 0; i < 50; i++) {
System.out.println("2打印:"+i);
if(i == 10){
try {
System.out.println("线程1加入到此线程!");
t1.join(); //在i==10时,让线程1加入,先完成线程1的内容,在继续当前内容
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
t1.start();
t2.start();
}
}

wait和notify方法

其实我们之前可能就发现了,Object类还有三个方法我们从来没有使用过,分别是wait()notify()以及notifyAll(),他们其实是需要配合synchronized来使用的(实际上锁就是依附于对象存在的,每个对象都应该有针对于锁的一些操作,所以说就这样设计了)当然,只有在同步代码块中才能使用这些方法,正常情况下会报错,我们来看看他们的作用是什么:

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
package org.wsy.juc;

public class JUC_07 {
public static void main(String[] args) throws InterruptedException {
Object o1 = new Object();
Thread t1 = new Thread(() -> {
synchronized (o1){
try {
System.out.println("开始等待");
o1.wait(); //进入等待状态并释放锁
System.out.println("等待结束!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
synchronized (o1){
System.out.println("开始唤醒!");
o1.notify(); //唤醒处于等待状态的线程
for (int i = 0; i < 50; i++) {
System.out.println(i);
}
//唤醒后依然需要等待这里的锁释放之前等待的线程才能继续
}
});
t1.start();
Thread.sleep(1000);
t2.start();
}
}

在InheritableThreadLocal存放的内容,会自动向子线程传递。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.wsy.juc;

public class JUC_09 {
public static void main(String[] args) {
ThreadLocal<String> local = new InheritableThreadLocal<>();
Thread t = new Thread(() -> {
local.set("yyds");
new Thread(() -> {
System.out.println(local.get());
}).start();
});
t.start();
}
}