系列文章目录
JavaEE初阶--------第三章 Thread类的基本用法
文章目录
前言
前面两章我们主要了解了什么是进程,什么是线程以及二者之间的一个区别。那么,到底该怎样去创建一个线程呢?这是我们这章主要要掌握的内容。
一、线程创建
使用 Thread 类,创建 Thread 对象,创建出一个线程出来。
方法一:继承 Thread 类
先上代码
class MyThread extends Thread{
@Override
public void run() {
//这个方法是线程的入口方法
while (true){
System.out.println("hello thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo1 {
public static void main(String[] args) {
Thread t = new MyThread();
//start 和 run 都是 Thread 的成员
//run 只是描述了线程的入口(线程要做什么任务)
//start 则是真正调用了系统api,在系统中创建出线程,让线程再调用 run
t.start();
while (true){
System.out.println("hello main!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
上面代码的运行结果就是hello main!和hello thread!一直死循环打印下去。可以看到,这两个 while 循环在“同时执行”,看到的结果,是两边的日志都在交替打印的效果,这也就实现了并发编程的效果。
- 首先要创建一个类来继承 Thread 类,紧接着是重写 run 方法,run 方法里面就是我们需要线程来执行的任务。接下来,在 main 方法中首先要 new 出一个实例对象 t ,然后,调用start 方法启动线程 t 。后面,就是主线程的代码了,执行主线程的任务。
- 这两线程都是休眠1000ms,当时间到了之后,这两线程谁先执行,谁后执行,就不一定了。这个过程可以视为是“随机”的。操作系统对于多个线程的调度顺序是不确定的,是“随机的”。
方法二:实现 Runnable 接口
class MyRunnable implements Runnable{
@Override
public void run() {
while (true){
System.out.println("hello Runnable!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
while (true){
System.out.println("hello main!");
Thread.sleep(1000);
}
}
}
- 实现 Runnable 接口,重写 run 方法。
- Runnable 表示的是一个“可以执行的任务”,这个任务是交给线程负责执行,还是交给其他的实体来执行,Runnable 本身并不关心。所以,还是要搭配 Thread 的构造方法来实现多线程。
- 使用 Runnable 的写法和直接继承 Thread 之间的区别,主要就是解耦合。(简单来说就是,减少模块之间的依赖性,提高程序的独立性)
- 创建一个线程,需要进行两个关键操作:1、明确线程要执行的任务。2、调用系统api 创建出线程。
方法三、使用匿名内部类(更简洁)
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(){
@Override
public void run() {
while (true){
System.out.println("hello thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread.start();
while (true){
System.out.println("hello main!");
Thread.sleep(1000);
}
}
}
public class Demo4 {
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (true){
System.out.println("hello thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
thread.start();
while (true){
System.out.println("hello main!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
- 上面这两种就是匿名内部类了,先创建出新的类,这个类的名字是啥不重要,因为这个类本身就是只用一次,同时又把这个子类的实例给创建出来。这里是子类,可以重写父类的方法。
- 使用匿名内部类的方式本质上和上面两种是一样的,只是会更加简洁一些罢了。
方法四:基于lambda 表达式(推荐)
public class Demo5 {
public static void main(String[] args) {
Thread thread = new Thread(() -> { //lambda表达式
while (true){
System.out.println("hello thread!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
thread.start();
while (true){
System.out.println("hello main!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
- 更简化的语法表达方式,“语法糖”,相当于是“匿名内部类”的替换写法。lambda表达式,本质上是一个匿名函数,主要是用来实现“回调函数”的效果。
- 回调函数,不是你主动调用的,也不是现在就立即调用,而是在后面合适的时机来调用这个函数。
二、线程终止
在Java中,要销毁/终止一个线程,做法比较唯一,就是想办法让run 方法尽快执行结束。
- 方案一
可以在代码中手动创建出标志位,来作为 run 的执行结束的条件。很多线程执行的时间久,往往是因为这里写了一个持续执行的循环,要想让 run 执行结束,就是要让循环尽快退出。
public class Demo6 {
private static boolean isQuit = false; //手动创建标志位
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() ->{
while (!isQuit){
System.out.println("线程工作中!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程工作完毕!");
});
thread.start();
Thread.sleep(5000);
isQuit = true;
System.out.println("设置isQuit为ture");
}
}
这里,我们就自己手动创建了一个标志位,当我们需要让线程终止时,就设置一下标志位,就可以将线程终止,
- 方案二(推荐)
Thread 类内部,有一个现成的标志位,可以用来判定当前的循环是否要结束。
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() ->{
while (!Thread.currentThread().isInterrupted()){
System.out.println("线程工作中!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break; //通过break,让线程立即结束
}
}
});
t.start();
Thread.sleep(5000);
System.out.println("让t 线程终止!");
t.interrupt();
}
}
-
首先获取到当前线程的实例,此处得到的就是 t ,Thread 内部有一个标志位,然后调用 isInterrupted( ) 方法就可以用来判断对象关联的线程的标志位是否设置,调用后不清楚标志位。
-
再调用 t.interrupt( ) 方法,把上述 Thread 对象内部的标志位设置为 true 了,这样就可以使线程终止了。
-
即使线程内部的逻辑出现阻塞(sleep),也是可以使用这个方法来唤醒的。正常来说,sleep 会休眠到时间到为止才能唤醒,此处给出的 interrupt 就可以使 sleep 内部触发一个异常,从而提前被唤醒,方案一中手动定义标志位,是无法实现这个效果的。
-
注意:interrupt 唤醒线程之后,此时 sleep 方法会抛出异常,但同时会自动清除刚才设置的标志位。这样,并不能达到终止线程的效果(如下图)。所以,要在 while 循环内加上 break ,来结束循环。
三、线程等待
- 让一个线程等待另一个线程执行结束,再继续执行,本质上就是控制线程结束的顺序。
public class Demo9 {
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) {
e.printStackTrace();
}
}
});
t.start();
System.out.println("join 开始等待!");
t.join();
System.out.println("join 结束等待!");
}
}
- 从上述运行结果可以看出,主线程调用了 t.join( ) 方法之后,主线程就不再往下运行了,而是去运行 t 线程中的代码,当 t 线程执行完毕以后,再接着回来执行主线程接下来的代码。
- t.join 工作过程:
1、如果 t 线程正在运行中,此时调用 join 的线程就会阻塞,一直阻塞到 t 线程执行结束为止。
2、如果 t 线程已经执行结束了,此时调用 join 线程,就直接返回了,不会涉及到阻塞。 - 一般来说,等待操作都是带有一个“超时时间”的。
public void join(long millis)
等待线程结束,最多等 millis 毫秒。
四、线程休眠
public static void sleep(long millis)throws InterruptedException
- Thread.sleep() 方法调用之后,系统会唤醒线程,线程由阻塞状态转变成就绪状态,这样线程也就可以到CPU 上继续运行了。
- 有一点要记住,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的。
五、获取线程实例
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() ->{
while (!Thread.currentThread().isInterrupted()){
System.out.println("线程工作中!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
});
}
这里的 Thread.currentThread( ) 就是获取到当前线程的实例,也就是 Theread t 。
- 哪个线程调用这个方法就会返回哪个线程的对象。
总结
这一章,我们主要总结了 Thread 类的基本用法,如何去创建一个线程,大致有三种方法(1.继承 Thread,重写run;2.实现 Runnable ,重写run;以及上述两种方法的匿名内部类 3.使用 lambda 表达式)。怎样去终止一个线程,如何让一个线程等待另一个线程,还有如何休眠一个线程,还有就是获取线程实例。通过这章的学习,我们就可以去完成一些线程的基本操作了、