28.java多线程(基础)

1.创建新线程

要创建一个新线程非常容易,我们需要实例化一个Thread实例,然后调用它的start()方法,下面提供三种方式创建:

  • Thread派生一个自定义类,然后覆写run()方法
  • 创建Thread实例时,传入一个Runnable实例
  • Java8引入的lambda语法进一步简写
#派生类
package thread;

public class Mythread extends Thread {
    @Override
    public void run() {
        System.out.println("我是自定义派生类");
    }
}

#实现Runnable
package thread;

public class MyTunable implements Runnable {
    @Override
    public void run()
    {
        System.out.println("传入Runnable实例");
    }
}

//方式1:从Thread派生一个自定义类,然后覆写run()方法
System.out.println("方式1");
Thread thread = new Mythread();
thread.start();

//方式2:创建Thread实例时,传入一个Runnable实例
System.out.println("方式2");
Thread thread1 = new Thread(new MyTunable());
thread1.start();

//方式3:Java8引入的lambda语法进一步简写
System.out.println("方式3");
Thread thread2 = new Thread(()->{
    System.out.println("java8 简写");
});
thread2.start();

#-------------输出--------------------
方式1
方式2
我是自定义派生类
方式3
传入Runnable实例
java8 简写

“方式1”,“方式2”,“方式3”的输出因为是在主线程,所以顺序肯定不变的,而“我是自定义派生类”,“传入Runnable实例”,“java8 简写”是新创建的线程,他们的顺序是按照执行时间输出的,可以加Thread.sleep()暂停查看结果。

2.线程状态

Java程序中,一个线程对象只能调用一次start()方法启动新线程,并在新线程中执行run()方法。一旦run()方法执行完毕,线程就结束了。 Java线程的状态有以下几种:

  • New:新创建的线程,尚未执行;
  • Runnable:运行中的线程,正在执行run()方法的Java代码;
  • Blocked:运行中的线程,因为某些操作被阻塞而挂起;
  • Waiting:运行中的线程,因为某些操作在等待中;
  • Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待;
  • Terminated:线程已终止,因为run()方法执行完毕。
         ┌─────────────┐
         │     New     │
         └─────────────┘
                │
                ▼
┌ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ┐
 ┌─────────────┐ ┌─────────────┐
││  Runnable   │ │   Blocked   ││
 └─────────────┘ └─────────────┘
│┌─────────────┐ ┌─────────────┐│
 │   Waiting   │ │Timed Waiting│
│└─────────────┘ └─────────────┘│
 ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─ ─
                │
                ▼
         ┌─────────────┐
         │ Terminated  │
         └─────────────┘

3.线程终止

  • 线程正常终止:run()方法执行到return语句返回;
  • 线程意外终止:run()方法因为未捕获的异常导致线程终止;
  • 对某个线程的Thread实例调用stop()方法强制终止(强烈不推荐使用)。

4.线程等待

一个线程还可以等待另一个线程直到其运行结束。使用join方法

Thread thread2 = new Thread(()->{
     System.out.println("java8 简写");
});
thread2.start();
//主线程等待thread2执行完成才会执行下面代码
thread1.join();
System.out.println("方式3");
#----------------输出--------------
java8 简写
方式3

5.中断线程

想要中断某个线程,只需要在其他线程中执行目标线程的interrupt()方法,目标线程需要反复检测自身状态是否是interrupted状态,如果是,就立刻结束运行。

  • 常规操作
Thread thread = new Thread(()->{
    System.out.println("执行新线程");
    int n = 0;
    while (!Thread.interrupted()) {
        n++;
        System.out.println(n);
    }
    System.out.println("线程执行完成了");
});
thread.start();//启动线程
System.out.println("线程启动");
Thread.sleep(1);//暂停10毫秒
thread.interrupt();//向thread线程发起中断请求
thread.join();// 等待thread线程执行完成
System.out.println("线程中断了");

t.join()会让main线程进入等待状态,等待thread线程执行完成

  • 新线程中启动其他线程的关闭
public class TestThread extends Thread {
    public void run() {
        System.out.println("我是TestThread派生类");
        int i = 0;
        while (!isInterrupted()) {
            i++;
            System.out.println(i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                break;
            }
        }
        System.out.println("TestThread执行完成了");
    }
}

public class Mythread extends Thread {
    @Override
    public void run() {
        System.out.println("我是Mythread派生类");
        Thread test = new TestThread();
        test.start();
        try {
            test.join();
        } catch (InterruptedException e) {
            System.out.println("test线程终止了");
        }
        test.interrupt();//发起终止申请,不加不能结束test线程
        System.out.println("Mythread线程执行完成");
    }
}

Thread thread = new Mythread();
thread.start();
System.out.println("启动thread线程");
Thread.sleep(1000);//为了显示thread线程执行情况
thread.interrupt();//中断thread线程通知
thread.join();//等待thread线程执行完成
System.out.println("主线程执行完成");

##-----------------输出-----------------
启动thread线程
我是Mythread派生类
我是TestThread派生类
1
2
3
4
5
6
7
8
9
10
test线程终止了
Mythread线程执行完成
TestThread执行完成了
主线程执行完成

主线程通过调用thread.interrupt()从而通知thread线程中断,而此时thread线程正位于test.join()的等待中,此方法会立刻结束等待并抛出InterruptedException。由于我们在thread线程中捕获了InterruptedException,因此,就可以准备结束该线程。在thread线程结束前,对test线程也进行了interrupt()调用通知其中断。如果去掉这一行代码,可以发现test线程仍然会继续运行,且JVM不会退出。

  • 设置标志位中断
    通过关键字volatile标记一个线程间共享变量boolean flag,然后在其他线程改变共享变量状态,子线程监听状态即可
public class Test2Thread extends Thread {
    public volatile boolean flag = true;
    public void run() {
        System.out.println("我是Test2Thread派生类");
        int i = 0;
        while (flag) {
            i++;
            System.out.println(i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                break;
            }
        }
        System.out.println("Test2Thread执行完成了");
    }
}

Test2Thread thread = new Test2Thread();
thread.start();
System.out.println("启动thread线程");
Thread.sleep(1000);
thread.flag = false;
System.out.println("主线程执行完成");

##---------------输出--------------------
启动thread线程
我是Test2Thread派生类
1
2
3
4
5
6
7
8
9
10
主线程执行完成
Test2Thread执行完成了

volatile关键字的目的是告诉虚拟机:

  • 每次访问变量时,总是获取主内存的最新值;
  • 每次修改变量后,立刻回写到主内存。

6. 守护线程

守护线程是Java中的一种特殊线程类型,它在后台为其他线程提供服务,当所有非守护线程结束时,JVM会自动退出,不管守护线程是否还在运行。

Thread t = new MyThread();
t.setDaemon(true);//开启守护线程
t.start();

在守护线程中,编写代码要注意:守护线程不能持有任何需要关闭的资源,例如打开文件等,因为虚拟机退出时,守护线程没有任何机会来关闭文件,这会导致数据丢失。

作用

  • 后台支持服务
    • 垃圾回收线程(GC)是最典型的守护线程
    • 内存管理
    • 日志记录
    • 监控统计
  • 避免程序无法终止
    • 确保当主线程结束时,后台线程不会阻止JVM退出
    • 适合执行不需要完整执行的任务
  • 系统级服务
    • 定时任务调度
    • 心跳检测
    • 缓存刷新
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容