JavaEE 多线程
JavaEE 多线程
文章目录
- JavaEE 多线程
- 引子
- 多线程
- 1. 特性
- 2. Thread类
- 2.1 概念
- 2.2 Thread的常见构造方法
- 2.3 Thread的几个常见属性
- 2.4 启动一个线程
- 2.5 中断一个线程
- 2.6 等待一个线程
- 2.7 获取当前线程引用
- 2.8 休眠当前线程
- 3. 线程状态
引子
当进入多线程这一块内容时,我们之前对代码的逻辑认知可能会被颠覆!
为什么这么说?让我们看看下面这段代码:
package demo1;
public class Test1 {
public static void main(String[] args) {
boolean judge = true;
while (judge) {
System.out.println("你出不去了!!");
}
judge = false;
System.out.println("我出来了!!");
}
}
从单线程的角度,这是一个逻辑闭环,死循环后面的代码无法被执行,它"永远都出不去"!代码会一直陷入循环之中:
但对于多线程来说,死循环也阻止不了它:
为什么能够做到这一点?这就涉及到多线程的特性了!
多线程
1. 特性
- 每个线程都是一个独立的执行流
- 多个线程之间是“并发”执行的
线程的调用方法我们使用lambda表达式
(之前的文章有对其进行讲解),看看下面的代码:
package demo1;
import static java.lang.Thread.sleep;
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
while (true) {
System.out.println("你好!");
try {
sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start(); // 在这里才开始创建线程
while (true) {
System.out.println("你也好!!");
sleep(1000);
}
}
}
从上述执行结果可以看出,两个线程都在并发执行,大大提高了程序整体的运行效率!!
注:
-
main函数本身为主线程
-
仔细观察可以发现,两个线程执行的先后顺序是不确定的,这和线程的
"随机调度"
有关:- 一个线程,什么时候被调度到cpu执行是不确定的
- 一个线程,什么时候从cpu上下来,给其它线程让位是不确定的
这种“随机调度”的特性很可能会造成"抢占式执行",从而造成线程安全问题
-
只有调用start()方法后t线程才会被创建
-
**sleep()**方法可以对线程进行休眠,参数以毫秒为单位
2. Thread类
2.1 概念
Thread类是JVM用来管理线程的一个类,且每个线程都有一个唯一的Thread对象与之关联
每个执行流需要有一个对象来描述,而Thread类的对象就是用来描述一个线程执行流的,JVM会将这些Thread对象组织起来,用于线程调度,线程管理。
2.2 Thread的常见构造方法
方法 | 说明 |
---|---|
Thread() | 创建线程对象 |
Thread(Runnable target) | 使用Runnable创建线程对象 |
Thread(String name) | 创建线程对象,并命名 |
Thread(Runnable target, String name) | 使用Runnable创建线程对象,并命名 |
Runnable runnable = new Runnable() {
@Override
public void run() {
}
};
Thread t1 = new Thread();
Thread t2 = new Thread(runnable);
Thread t3 = new Thread("线程3");
Thread t4 = new Thread(runnable, "线程4");
2.3 Thread的几个常见属性
属性 | 获取方法 |
---|---|
ID | getId() |
名称 | getName() |
状态 | getState() |
优先级 | getPriority() |
是否后台进程 | isDaemon() |
是否存活 | isAlive() |
是否被中断 | isInterrupted() |
-
ID是线程的唯一标识,不同线程不会重复
-
优先级高的线程理论上来说更容易被调度到
-
是否存活,可以理解为run方法是否运行结束了
-
JVM会在一个进程的所有非后台线程结束后,才会结束运行:
// 默认线程t为前台线程,前台线程未结束时,整个进程不会结束 package demo2; public class Test5 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { for (int i = 0;i < 10;i++) { try { System.out.println(Thread.currentThread().getName() + ":本线程还活着"); Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName() + ":本线程将消失"); }); System.out.println("ID-" + t.getId()); System.out.println("名称-" + t.getName()); System.out.println("状态-" + t.getState()); System.out.println("优先级-" + t.getPriority()); System.out.println("后台线程-" + t.isDaemon()); t.start(); for (int j = 0;j < 5;j++) { System.out.println("我是主线程"); Thread.sleep(1000); } System.out.println("主线程结束了"); } }
// 这里修改t为后台线程,则主线程结束整个进程就结束 package demo2; public class Test5 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { for (int i = 0;i < 10;i++) { try { System.out.println(Thread.currentThread().getName() + ":本线程还活着"); Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(Thread.currentThread().getName() + ":本线程将消失"); }); t.setDaemon(true); // 这里修改t为后台线程,则主线程结束整个进程就结束 System.out.println("ID-" + t.getId()); System.out.println("名称-" + t.getName()); System.out.println("状态-" + t.getState()); System.out.println("优先级-" + t.getPriority()); System.out.println("后台线程-" + t.isDaemon()); t.start(); for (int j = 0;j < 5;j++) { Thread.sleep(2000); System.out.println("我是主线程"); } System.out.println("主线程结束了"); } }
2.4 启动一个线程
之前我们通过覆写run方法创建了一个线程对象,但线程对象被创建出来并不代表着线程就开始运行了,我们需要通过调用start()方法
才真正在操作系统的底层创建出一个线程:
public class Test6 {
public static void main(String[] args) {
Thread t = new Thread(()->{
System.out.println("hh");
});
// 此时线程并未完全创建
}
}
调用t.start()
方法后,才算创建线程成功,同时自动调用run方法(被覆写):
2.5 中断一个线程
目前常见的中断线程方式有以下两种:
-
用共享的标记来进行沟通
package demo2; public class Test7 { public static volatile boolean isQuit = false; // 这里需要给标志位加上volatile关键字 public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { while (!isQuit) { System.out.println("我是一个线程,正在工作中"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); for (int i = 5;i >= 0;i--) { System.out.println("倒计时: " + i); Thread.sleep(1000); } System.out.println("让线程结束工作"); isQuit = true; System.out.println("线程工作结束!"); } }
-
使用thread对象的interrupted()方法来通知线程结束
方法 说明 Thread.currentThread().isInterrupted() 判断当前线程中断标志是否设置 Thread.currentThread().Interrupt() 设置中断标志中断该线程 注:
Thread.currentThread()
操作是获取当前的线程实例(t),哪个线程调用,得到的就是哪个线程的实例package demo2; public class Test8 { public static void main(String[] args) throws InterruptedException { Thread t = new Thread(() -> { while (!Thread.currentThread().isInterrupted()) { try { System.out.println("我是一个线程,正在工作中"); Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }); t.start(); for (int i = 5;i >= 0;i--) { System.out.println("倒计时: " + i); Thread.sleep(1000); } System.out.println("让线程结束工作"); t.interrupt(); System.out.println("线程工作结束!"); } }
注:thread收到通知的方式有两种:
-
如果线程因为调用wait/join/sleep等方法而阻塞挂起,则以
InterruptedException
异常的形式通知,同时清除中断标准;当出现
InterruptedException
的时候,要不要结束线程取决于catch中代码的写法,可以选择忽略这个异常,也可以通过break
跳出循环结束线程; -
如果只是内部的一个中断标志被设置,thread可以通过;
Thread.currentThread().isInterrupted()
判断指定线程的中断标志是否设置,不清除中断标志,这种方式通知收到的更及时,即使线程正在sleep也可以马上收到。
2.6 等待一个线程
有时候一个线程需要等待另一个一个线程完成它的工作后,才能进行自己的下一步工作。这个时候可以通过join()
方法来进行线程等待。
join(): 在哪个线程中调用join方法则当前线程要等待引用线程执行完后才能执行,如在主线程中调用t.join();则主线程要等待t线程执行完后才能执行
package demo2;
public class Test9 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0;i < 5;i++) {
System.out.println("t-线程工作中!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
t.join();
System.out.println("main 执行了");
}
}
join()方法也可以设置时间限制,最多等待X毫秒,时间一到线程就停止阻塞等待:
t.join(2000);
2.7 获取当前线程引用
public static Thread currentThread(); 返回当前线程对象的引用
package demo2;
public class Test10 {
public static void main(String[] args) {
Thread thread = Thread.currentThread();
System.out.println(thread.getName());
}
}
2.8 休眠当前线程
public static void sleep (long millis) throws InterruptedException; 休眠当前进程millis毫秒
注:因为线程的调度是不可控的,所有这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的
package demo2;
public class Test11 {
public static void main(String[] args) throws InterruptedException {
System.out.println("开始时间:" + System.currentTimeMillis());
Thread.sleep(3000); // 休眠主线程
System.out.println("结束时间:" + System.currentTimeMillis());
}
}
3. 线程状态
- NEW: Thread对象创建好了,但是还没有调用start方法在系统中创建线程;
- RUNNABLE:就绪状态,表示这个线程正在cpu上执行或准备就绪随时可以去cpu上执行;
- TIME_WATING: 表示指定时间的阻塞,达到一定时间后会自动解除阻塞,一般是调用
sleep方法
或有时间参数的join方法
时会进入该状态; - WATINGT: 表示不带时间的阻塞(死等),必须要满足一定条件才会解除阻塞,一般调用
wait方法
或join方法
会进入该状态; - BLOCKED: 锁竞争引起的阻塞;
- TERMINATED: Thread对象仍然存在,但是系统内部的线程已经执行完毕了