线程

线程

1. 创建线程

1.1 继承 Thread 类

步骤

  1. 定义一个线程类继承 Thread 类
  2. 重写 run 方法,里面是定义线程以后要干啥
  3. new 一个新线程对象
  4. 调用 start 方法启动线程(执行的还是run方法)
/**
 * 方法一、通过继承Thread
 *
 * @author Enndfp
 */
public class ThreadDemo {
    public static void main(String[] args) {
        // 3、new一个新线程对象
        Thread t = new MyThread();
        // 4、调用start方法启动线程
        t.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("主线程执行输出:" + i);
        }
    }
}

// 1、定义一个线程类继承Thread类
class MyThread extends Thread {
    // 2、重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("子线程执行输出:" + i);
        }
    }
}

优缺点

  • 优点:编码简单
  • 缺点:
    • 线程类已经继承了 Thread 类无法继承其他类了,功能不能通过继承拓展(单继承的局限性)
    • 每个继承 Thread 类的对象都是一个独立的线程对象,占用一定的系统资源

1.2 实现 Runnable 接口

步骤

  1. 定义一个线程任务类,实现 Runnable 接口
  2. 重写 run 方法,定义线程的执行任务
  3. 创建一个任务对象
  4. 把任务对象交给Thread处理
  5. 启动线程
/**
 * 方法二、通过实现Runnable接口
 *
 * @author Enndfp
 */
public class RunnableDemo {
    public static void main(String[] args) {
        // 3、创建一个任务对象
        Runnable target = new MyRunnable();
        // 4、把任务对象交给Thread处理
        Thread t = new Thread(target);
        // 5、启动线程
        t.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("主线程执行输出:" + i);
        }
    }
}

// 1、定义一个线程任务类,实现Runnable接口
class MyRunnable implements Runnable {
    // 2、重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("子线程执行输出:" + i);
        }
    }
}

Thread 类本身也是实现了 Runnable 接口,Thread 类中持有 Runnable 的属性,执行线程 run 方法底层是调用 Runnable 的 run:

public class Thread implements Runnable {
    private Runnable target;

    public void run() {
        if (target != null) {
            // 底层调用的是 Runnable 的 run 方法
            target.run();
        }
    }
}

优缺点

  • 缺点:代码复杂一点
  • 优点:
    • 线程任务类只是实现了 Runnable 接口,可以继续继承其他类,避免了单继承的局限性
    • 多个线程可以共享同一个 Runnable 对象,减少了资源占用
    • 实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立

1.3 实现 Callable 接口

步骤

  1. 定义一个任务类,实现 Callable 接口(应该声明线程任务执行完毕后结果的数据类型)
  2. 重写 call 方法(任务方法)
  3. 创建 Callable 任务对象
  4. 把 Callable 任务对象 交给 FutureTask 对象
  5. 交给线程处理
  6. 启动线程

补充

FutureTask对象的作用1: 是 Runnable 的对象(实现了 Runnable 接口),可以交给 Thread 了

FutureTask对象的作用2: 可以在线程执行完毕之后通过调用其 get 方法得到线程执行完成的结果

/**
 * 方法三、通过实现Callable接口
 *
 * @author Enndfp
 */
public class CallableDemo {
    public static void main(String[] args) {
        // 3、创建Callable任务对象
        Callable<String> call = new MyCallable();
        // 4、把Callable任务对象 交给 FutureTask 对象
        FutureTask<String> task = new FutureTask<>(call);
        // 5、交给线程处理
        Thread t = new Thread(task);
        // 6、启动线程
        t.start();

        try {
            // 如果task任务没有执行完毕,这里的代码会等待,直到线程跑完才提取结果
            String result = task.get();
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// 1、定义一个任务类,实现Callable接口
class MyCallable implements Callable {
    // 2、重写call方法(任务方法)
    @Override
    public String call() throws Exception {
        return "子线程执行的结果是:Hello World!";
    }
}

优缺点

  • 优点:同Runnable,并且可以得到线程执行的结果
  • 缺点:编码复杂

2. 查看进程和线程的方法

2.1 Windows

  • 任务管理器可以查看进程和线程数,也可以用来杀死进程
  • tasklist 查看进程
  • taskkill 杀死进程

2.2 Linux

  • ps -ef 查看所有进程
  • top 定位哪个进程对 CPU 占用过多
  • ps H -eo pid,tid,%cpu | grep PID 定位是哪个线程引起的 CPU 占用过高
  • jstack PID 根据进程 id 找到有问题的线程
  • kill 杀死线程

2.3 Java

  • jps 查看所有Java进程
  • jstack 查看某个 Java 进程(PID)的所有线程状态
  • jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)

jconsole 远程监控配置

  • 需要以如下方式运行你的 java 类
java -Djava.rmi.server.hostname=ip地址 -Dcom.sun.management.jmxremote -
Dcom.sun.management.jmxremote.port=连接端口 -Dcom.sun.management.jmxremote.ssl=是否安全连接 -
Dcom.sun.management.jmxremote.authenticate=是否认证 java类
  • 修改 /etc/hosts 文件将 127.0.0.1 映射至主机名

如果要认证访问,还需要做如下步骤

  • 复制 jmxremote.password 文件
  • 修改 jmxremote.password 和 jmxremote.access 文件的权限为 600 即文件所有者可读写
  • 连接时填入 controlRole(用户名),R&D(密码)

3. 线程常见 API

方法 说明
public void start() 启动一个新线程,在新的线程运行 run 方法中的代码
public void run() 线程启动后调用该方法
public void setName(String name) 给当前线程取名字
public void getName() 获取当前线程的名字
线程存在默认名称:子线程是 Thread-索引,主线程是 main
public static Thread currentThread() 获取当前线程对象,代码在哪个线程中执行
public static void sleep(long time) 让当前线程休眠多少毫秒再继续执行
Thread.sleep(0) : 让操作系统立刻重新进行一次 CPU 竞争
public static native void yield() 提示线程调度器让出当前线程对 CPU 的使用
public final int getPriority() 返回此线程的优先级
public final void setPriority(int priority) 更改此线程的优先级,常用 1 5 10
public void interrupt() 中断这个线程,异常处理机制
public static boolean interrupted() 判断当前线程是否被打断,清除打断标记
public boolean isInterrupted() 判断当前线程是否被打断,不清除打断标记
public final void join() 等待这个线程结束
public final void join(long millis) 等待这个线程死亡 millis 毫秒,0 意味着永远等待
public final native boolean isAlive() 线程是否存活(还没有运行完毕)
public final void setDaemon(boolean on) 将此线程标记为守护线程或用户线程

3.1 start 与 run

start() 方法

  • start()Thread 类的方法,用于启动一个新的线程。当调用 start() 方法时,会创建一个新的线程,并且在新线程中调用 run() 方法
  • 在新线程中执行 run() 方法,不会阻塞主线程的继续执行。这使得程序可以同时执行多个任务
  • start() 方法的调用后,会导致系统自动安排新线程的执行。线程调度器决定了各个线程的执行顺序和时间片分配

run() 方法

  • run() 方法是 Thread 类中用于定义线程任务的方法。当直接调用 run() 方法时,它会在当前线程(通常是主线程)中执行,而不会创建新的线程
  • 如果你调用了 run() 方法而不是 start() 方法,那么 run() 方法会在当前线程中运行,会阻塞主线程,直到 run() 方法执行完毕才会继续执行主线程
  • run() 方法通常被重写,用于定义线程的实际逻辑,例如,在一个多线程应用程序中,不同线程可能会执行不同的任务

补充

  1. start() 方法只是让线程进入就绪,里面代码不一定立刻运行(CPU 的时间片还没分给它)。每个线程对象的 start() 方法只能调用一次,如果调用了多次会出现 IllegalThreadStateException

  2. 当使用 start() 方法启动线程时,不会直接传递给调用 start() 的地方,线程的异常处理需要使用特定的机制(Thread.UncaughtExceptionHandler)来捕获未捕获的异常

  3. 在使用 run() 方法执行线程任务时,异常会传播到调用方,可以使用常规的 try-catch 块来捕获和处理异常

3.2 sleep 与 yield

sleep () 方法

  • 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  • 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  • 睡眠结束后的线程未必会立刻得到执行,需要抢占 CPU
  • 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性
  • sleep() 方法的过程中,线程不会释放对象锁

yield() 方法

  • 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
  • 具体的实现依赖于操作系统的任务调度器
  • 会放弃 CPU 资源,锁资源不会释放

补充

sleep() 方法会导致调用线程进入阻塞状态,暂停执行指定的时间。在此期间,调用线程不会占用 CPU 资源,但其他线程仍然可以继续执行

yield() 方法通常用于鼓励公平竞争和避免某些线程长时间占用 CPU 而导致其他线程无法执行,但并不保证其他线程一定会执行

3.3 join

join () 方法

  • join() 方法是一个阻塞方法,会阻塞当前线程的执行,直到被调用的线程执行完成。如果被调用的线程在执行过程中发生异常被中断,也会抛出 InterruptedException 异常

3.4 interrupt

interrupt() 方法

  • interrupt() 方法用于中断一个正在运行的线程,即向目标线程发送一个中断信号。这个信号会被目标线程接收到,并可以在适当的时机中断线程的执行。但是需要注意,interrupt() 方法并不会强制终止线程,而是提供一种协作机制,线程可以检查自己是否被中断,然后自行决定是否终止

  • 打断的线程会发生上下文切换,操作系统会保存线程信息,抢占到 CPU 后会从中断的地方接着运行(打断不是停止

  • sleep、wait、join 方法都会让线程进入阻塞状态,打断线程会清空打断状态(false)

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(()->{
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }, "t1");
    t1.start();
    Thread.sleep(500);
    t1.interrupt();
    System.out.println(" 打断状态: " + t1.isInterrupted());// 打断状态: false
}
  • 打断正常运行的线程:不会清空打断状态(true)
public static void main(String[] args) throws Exception {
    Thread t2 = new Thread(()->{
        while(true) {
            Thread current = Thread.currentThread();
            boolean interrupted = current.isInterrupted();
            if(interrupted) {
                System.out.println(" 打断状态: " + interrupted);//打断状态: true
                break;
            }
        }
    }, "t2");
    t2.start();
    Thread.sleep(500);
    t2.interrupt();
}

3.5 park

park 作用类似 sleep,打断 park 线程,不会清空打断状态(true)

public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("park...");
            LockSupport.park();
            log.debug("unpark...");
            log.debug("打断状态:{}", Thread.currentThread().isInterrupted()); //打断状态:true
        }, "t1");

        t1.start();
        Thread.sleep(2000);
        t1.interrupt();
    }

如果打断标记已经是 true, 则 park 会失效

Thread t1 = new Thread(() -> {
            log.debug("park...");
            LockSupport.park();
            log.debug("unpark...");
            log.debug("打断状态:{}", Thread.currentThread().isInterrupted()); //打断状态:true

            LockSupport.park(); //失效,不会阻塞
            log.debug("unpark..."); //和上一个unpark同时执行
        }, "t1");

可以修改获取打断状态方法,使用 Thread.interrupted(),清除打断标记

3.6 daemon

public final void setDaemon(boolean on):如果是 true ,将此线程标记为守护线程

线程启动前调用此方法:

Thread t = new Thread() {
    @Override
    public void run() {
        log.debug("running...");
    }
};
// 设置该线程为守护线程
t.setDaemon(true);
t.start();

用户线程:平常创建的普通线程

守护线程:服务于用户线程,只要其它非守护线程运行结束了,即使守护线程代码没有执行完,也会强制结束。守护进程是脱离于终端并且在后台运行的进程,脱离终端是为了避免在执行的过程中的信息在终端上显示

说明:当运行的线程都是守护线程,Java 虚拟机将退出,因为普通线程执行完后,JVM 是守护线程,不会继续运行下去

常见的守护线程:

  • 垃圾回收器线程就是一种守护线程
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

3.7 过时

不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁:

  • public final void stop():停止线程运行

    废弃原因:方法粗暴,除非可能执行 finally 代码块以及释放 synchronized 外,线程将直接被终止,如果线程持有 JUC 的互斥锁可能导致锁来不及释放,造成其他线程永远等待的局面

  • public final void suspend()挂起(暂停)线程运行

    废弃原因:如果目标线程在暂停时对系统资源持有锁,则在目标线程恢复之前没有线程可以访问该资源,如果恢复目标线程的线程在调用 resume 之前会尝试访问此共享资源,则会导致死锁

  • public final void resume():恢复线程运行

4. 线程状态

操作系统层面:初始状态、可运行状态、运行状态、阻塞状态、终止状态

线程由生到死的完整过程(生命周期):当线程被创建并启动以后,并不是一启动就进入了运行状态,也不是一直处于运行状态,在 Java APIjava.lang.Thread.State 这个枚举中给出了六种线程状态:

线程状态 导致状态发生条件
NEW(新建) 线程刚被创建,但是并未启动,还没调用 start 方法,只有线程对象,没有线程特征
Runnable(可运行) 线程可以在 Java 虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器,调用了 t.start() 方法:就绪(经典叫法)
Blocked(阻塞) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入 Blocked 状态;当该线程持有锁时,该线程将变成 Runnable 状态
Waiting(无限等待) 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入 Waiting 状态,进入这个状态后不能自动唤醒,必须等待另一个线程调用 notify 或者 notifyAll 方法才能唤醒
Timed Waiting (限期等待) 有几个方法有超时参数,调用将进入 Timed Waiting 状态,这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有 Thread.sleep 、Object.wait
Teminated(终止) run 方法正常退出而死亡,或者因为没有捕获的异常终止了 run 方法而死亡

image-20230811110930992

  • NEW → RUNNABLE:当调用 t.start() 方法时,由 NEW → RUNNABLE
  • RUNNABLE <--> WAITING:
    • 调用 obj.wait() 方法时
    • 调用 obj.notify()、obj.notifyAll()、t.interrupt():
    • 竞争锁成功,t 线程从 WAITING → RUNNABLE
    • 竞争锁失败,t 线程从 WAITING → BLOCKED
    • 当前线程调用 t.join() 方法,注意是当前线程在 t 线程对象的监视器上等待
    • 当前线程调用 LockSupport.park() 方法
  • RUNNABLE <--> TIMED_WAITING:调用 obj.wait(long n) 方法、当前线程调用 t.join(long n) 方法、当前线程调用 Thread.sleep(long n)
  • RUNNABLE <--> BLOCKED:t 线程用 synchronized(obj) 获取了对象锁时竞争失败
🌟 如果您喜欢我的文章,欢迎赞赏支持,您的支持是我创作的最大动力!🌟
🖋 作者:Enndfp
🔗链接:https://blog.enndfp.cn
📜版权声明:您可以自由转载,但请务必注明原文地址,感谢您的尊重与支持~
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇