Java多线程

Java多线程

进程,线程以及多线程

想学习线程首先要搞懂进程,进程指的就是我们正在运行的某个应用程序,例如 QQ,微信以及自己编写的 java 程序运行起来之后都属于一个进程

线程是最小的运算调度单位,一个进程要求至少拥有一个线程,在单核处理器的情况下多线程是以交替形式运行的,并不是同时运行,只是切换的速度比较快而已,多核处理器也是同理,每个处理器都可以调用线程,可以实现同时运行

我们用 java 书写的控制台程序,它本身就是一个进程,mian 函数就是主线程,我们所有的代码都是基于 mian 线程按照顺序往下跑的,有些时候有那种计算量比较大的函数就会很浪费时间,这个时候我们就可以在创建一个线程将这个函数丢给他调用,就不影响主函数的执行时间了,例如发送邮件的时间延迟,我们就可以通过多线程的异步特点来实现

Thread 线程类常用方法

类型 方法 作用
实例 start(); 激活线程开始运行
实例 run(); 线程类必须复写的函数,包含该线程需要完成的逻辑代码等
实例 setName(String name) 修改当前线程名称
实例 setPriority(int priority) 设置线程的优先级
实例 setDaemon(boolean on) 设置守护守护线程 ( true ) 和用户线程 ( fasse )
实例 String getName() 获取当前线程名称
实例 int getPriority() 获取线程的优先级
实例 join() 暂定当前线程,由调用者开始下一次执行
实例 isAlive() 检测线程是否处于活动状态
—— ———————————— ——————————————————————————
静态 yield() 礼让:停止当前正在运行的线程,调用者插队执行
静态 sleep(long time) 睡眠:当前线程暂停运行,time 毫秒后继续执行
静态 boolean holdsLock(Object x) 当前线程在指定的对象上保持监视锁时反会 true
静态 Thread currentThread() 返回当前正在执行的线程

线程的创建方式

线程的创建有很多种,这里简单介绍常用的几种

继承 Thread 类

四部曲:继承 Thread 类 → 复写 run 方法 → 创建当前类对象 ( 多态:Thread 类型 ) → start 启动线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 多线程第一种实现方式:继承Thread类复写run函数,run函数内就是多线程执行的任务了。
public class MyThread1 extends Thread{
public static int count = 300;
public static void main(String[] args) {
new MyThread1().start();
for (int i = 0; i < count; i++) {
System.out.println("main 执行了" + (i + 1) + "次");
}

}
@Override
public void run() {
for (int i = 0; i < count; i++) {
System.out.println("【多线程执行】" + (i + 1) + "次");
}
}
}

实现 Runnable 接口

四部曲:实现 Runnable 接口 → 复写 run 方法 → 创建线程类对象 ( 将当前类示例交给他 ) → start 启动线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyThread2 implements Runnable {
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("【多线程执行】" + (i + 1) + "次");
}
}
public static void main(String[] args) {
new Thread( new MyThread2() ).start();
for (int i = 0; i < 200; i++) {
System.out.println("main 执行了" + (i + 1) + "次");
}
}
}

上述代码可以使用 Lambda 表达式快速创建线程:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MyThread2 {
public static void main(String[] args) {
int count = 300;
new Thread( ()->{
for (int i = 0; i < count; i++) {
System.out.println("【多线程执行】执行了" + (i + 1) + "次");
}
}).start();
for (int i = 0; i < count; i++) {
System.out.println("main 执行了" + (i + 1) + "次");
}
}
}

继承 Thread 类和实现 Runnable 接口的区别

我们来写一个卖票的小练习来了解一下两种线程创建的区别

Runnable 接口实现的多线程:

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
// 最终数据可能会出现些许偏差,之后在讲
public class MyBuyTicket implements Runnable{

public int ticketNum = 20;

public static void main(String[] args) {
MyBuyTicket mb = new MyBuyTicket();
Thread t1 = new Thread(mb);
Thread t2 = new Thread(mb);
Thread t3 = new Thread(mb);
t1.setName("老八");
t2.setName("卢本伟");
t3.setName("菜虚鲲");
t1.start();
t2.start();
t3.start();
}

@Override
public void run() {
while ( ticketNum > 0 ){
ticketNum--;
// currentThread获取当前运行的线程,getName获取线程的名称
System.out.println("【" +
Thread.currentThread().getName() + "】抢到了一张票");
}
}

}

Thread 类实现的多线程

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
public class MyBuyTicket extends Thread{

public int ticketNum = 20;

public static void main(String[] args) {
Thread t1 = new MyBuyTicket();
Thread t2 = new MyBuyTicket();
Thread t3 = new MyBuyTicket();
t1.setName("老八");
t2.setName("卢本伟");
t3.setName("菜虚鲲");
t1.start();
t2.start();
t3.start();
}

@Override
public void run() {
while ( ticketNum > 0 ){
ticketNum--;
System.out.println("【" +
Thread.currentThread().getName() + "】抢到了一张票");
}
}

}

通过这个小练习我们可以看出,Thread 每创建一个线程相应的就必须创建一个对象,而 Runnable 可以实现多个线程操作同一个对象,共享对象内的数据,使用多线程的情况下,推荐优先使用 Runnable 接口

线程的生命周期

线程从创建到结束共分为大概五个生命周期:

  1. 【新建】:线程被创建出来,也就是 new Thread()
  2. 【就绪】:线程已经创建完毕并且启动了,也就是已经调用 start() 方法了,随时等待 CPU 的调度
  3. 【运行】:线程被 CPU 调度,正在执行 run 方法内的逻辑
  4. 【阻塞】:没有执行的资格和执行权利,例如 sleep(long time)
  5. 【销毁】:线程任务已经完成,没有再次使用的地方,线程的对象变成垃圾,释放资源。

正常线程的流程就是【新建】→【就绪】→【运行】→【可能会有阻塞】→【就绪】→【运行】…→【销毁】

线程睡眠

线程睡眠是指暂时让线程停止运行,在约定时间内不接收 CPU 的调度,用法:Thread.sleep(long time)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 通过线程睡眠实现每秒打印一句话
public class MyThread3 implements Runnable{
@Override
public void run() {
for ( int time = 1; time <= 10; time++ ){
System.out.println( "本站已不间断运行" + time + "秒");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("线程停止失败!");
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new Thread(new MyThread3()).start();
}
}

结束运行

让线程停止运行,JDK 提供了 stop 实例函数进行停止:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 练习线程停止
public class MyThread3 implements Runnable{
@Override
public void run() {
for ( int count = 1; true; count++ )
System.out.println("线程跑了" + count + "次");
}
public static void main(String[] args) throws InterruptedException {
MyThread3 mt = new MyThread3();
Thread t1 = new Thread(mt);
t1.start();
Thread.sleep(1000);
t1.stop();
}
}

经测试这样是可以停止线程的,但是官方并不建议我们这么使用,推荐自己定义一个标识在外部控制停止:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class MyThread3 implements Runnable{
public boolean flag = true;
@Override
public void run() {
for ( int count = 1; flag; count++ )
System.out.println("线程跑了" + count + "次");
}
public void stop(){
this.flag = false;
System.out.println("线程停止成功");
}
public static void main(String[] args) throws InterruptedException {
MyThread3 mt3 = new MyThread3();
new Thread(mt3).start();
Thread.sleep(1000);
mt3.stop();
}
}

线程礼让

我们都知道线程是被 CPU 随机调度的,两个线程在一起的时候,如果想让另一个执行的多一点可以使用线程礼让,线程礼让属于阻塞的函数,需要注意的是,礼让仅仅是放弃当此调度,CPU 会重新从两个线程中再选出一个进行调度,选中的可能还是之前的线程,也就是说,线程礼让并不会 100% 成功

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 线程礼让
public class MyThread3 implements Runnable{
@Override
public void run() {
for ( int i = 1; i <= 100; i++ ){
// 老八助人为乐选择礼让
if( i >= 90 && Thread.currentThread().getName().equals("老八") ){
System.out.println("【老八选择礼让】");
Thread.yield();
}
System.out.println( "【" + Thread.currentThread().getName()
+ "】运行了" + i + "次");
}
}
public static void main(String[] args) {
MyThread3 mt3 = new MyThread3();
Thread t1 = new Thread(mt3);
Thread t2 = new Thread(mt3);
t1.setName("老八");
t2.setName("菜虚鲲");
t1.start();
t2.start();
}

线程等待

正在运行的线程中如果调用了 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
public class MyThread3 implements Runnable{
@Override
public void run() {
for ( int i = 1; i <= 1000; i++ ){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println( "【" + Thread.currentThread().getName()
+ "】运行了" + i + "次");
}
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new MyThread3());
Thread t2 = new Thread(new MyThread3());
t1.setName("老八");
t2.setName("菜虚鲲");
t1.start();
t2.start();
for (int i = 0; i < 1000; i++) {
Thread.sleep(1);
System.out.println( "【main线程】运行了" + i + "次");
if ( i == 500 ){
System.out.println("main已经跑到500,T来插个队");
// mian函数中调用了join,mian停止运行,调用者t1开始下次执行
t1.join();
}
}
}
}

线程优先级

当我们创建了多个线程同时启动的时候,他们会被 CPU 随机调度,每次执行的顺序都不同,这里我们可以通过设置优先级来决定执行的先后顺序

setPriority(int priority):设置线程的优先级,需要 int 类型的值作为优先级参数,默认优先级为 5,取值范围为 1-10,数值越大对应的优先级越高,可以通过 setPriority() 来获取线程优先级,需要注意的是优先级高的线程会在调度时被给予优先,但这不代表启动后就会运行

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 class MyThread3 extends Thread{

public String msg;

public MyThread3(String msg) { this.msg = msg; }

@Override
public void run() {
System.out.println(this.msg);
}

public static void main(String[] args) {
Thread t1 = new MyThread3("我想第1个执行");
Thread t2 = new MyThread3("我想第2个执行");
Thread t3 = new MyThread3("我想第3个执行");
Thread t4 = new MyThread3("我想第4个执行");
Thread t5 = new MyThread3("我想第5个执行");
t1.setPriority(5);
t2.setPriority(4);
t3.setPriority(3);
t4.setPriority(2);
t5.setPriority(1);
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}

}

误区:并不是优先级高执行结束后在执行优先级低的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyThread3 extends Thread{

public MyThread3(String name) { super(name); }

@Override
public void run() {
for (int count = 0; count < 1000; count++)
System.out.println("【" + this.getName() + "】执行了" + count + "次");
}

public static void main(String[] args) {
Thread t1 = new MyThread3("老八");
Thread t2 = new MyThread3("菜虚鲲");
t1.setPriority(6);
t1.start();
t2.start();
}

}

用户线程和守护线程

线程中包含了两种类型,为用户线程和守护线程,用户线程可以在任务未完成的情况下可以无休止的运行,而守护线程则是主线程 ( mian ) 运行结束后关机 JVM 虚拟机时就跟着停止运行了,我们创建的线程没有修改过的话默认都是用户线程,修改线程类型的函数为 setDaemon(),用户线程为 false,守护线程为 true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 测试用户线程
public class MyThread4 extends Thread{

@Override
public void run() {
for (int i = 1; i <= 100000; i++)
System.out.println("线程运行了" + i + "次");
}

public static void main(String[] args) throws InterruptedException {
// 创建的线程默认就属于用户线程,不需要修改什么东西
Thread t1 = new MyThread4();
t1.start();
}

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 测试守护线程
public class MyThread4 extends Thread{

@Override
public void run() {
for (int i = 1; i <= 100000; i++)
System.out.println("线程运行了" + i + "次");
}

public static void main(String[] args) throws InterruptedException {
Thread t1 = new MyThread4();
// 这里将线程设置为守护类型
t1.setDaemon(true);
t1.start();
// 设置了500ms延时防止线程未执行
Thread.sleep(500);
}

}

结果:用户线程等待 10w 条数据打印完成才结束程序,而守护线程打印到 4w 多的时候就结束了程序


Java多线程
http://example.com/2021/04/13/Java笔记/线程技术/
Author
Shi_Kang
Posted on
April 13, 2021
Licensed under