Java多线程API

目录

1、创建并运行线程

1.1 直接使用Thread

1.2 使用Runnable配合Thread(把线程和任务进行解耦)

1.3 FutureTask配合Thread(FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况)

2、查看进程和线程的相关命令

2.1 Linux命令

2.2 Java命令及工具

3、线程运行的原理

3.1 栈与栈帧

3.2 线程上下文切换(Thread Context Switch)

4、Java多线程API

5、start与run

6、sleep与yield

7、join方法

8、interrupt方法

9、不推荐的方法


1、创建并运行线程

1.1 直接使用Thread

// 构造方法的参数是给线程指定名字,推荐
Thread t1 = new Thread("t1") {
    @Override
    // run 方法内实现了要执行的任务
    public void run() {
        log.debug("do something");
    }
};
t1.start();

1.2 使用Runnable配合Thread(把线程和任务进行解耦)

// 创建任务对象
Runnable task2 = new Runnable() {
    @Override
    public void run() {
        log.debug("do something");
    }
};
// 参数1 是任务对象; 参数2 是线程名字,推荐
Thread t2 = new Thread(task2, "t2");
t2.start();

1.3 FutureTask配合Thread(FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况)

// 创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(() -> {
    log.debug("do something");
    return 100;
});
// 参数1 是任务对象; 参数2 是线程名字,推荐
new Thread(task3, "t3").start();
// 主线程阻塞,同步等待 task 执行完毕的结果
Integer result = task3.get();
log.debug("结果是:{}", result);

2、查看进程和线程的相关命令

2.1 Linux命令

  • ps -fe 查看所有进程
  • ps -fT -p <PID> 查看某个进程(PID)的所有线程
  • kill 杀死进程
  • top 按大写 H 切换是否显示线程
  • top -H -p <PID> 查看某个进程(PID)的所有线程

2.2 Java命令及工具

  • jps 命令查看所有 Java 进程
  • jstack <PID> 查看某个 Java 进程(PID)的所有线程状态
  • jconsole或jvisualvm等工具 来查看某个 Java 进程中线程的运行情况(图形界面)
     

3、线程运行的原理

3.1 栈与栈帧

Java Virtual Machine Stacks (Java 虚拟机栈)
我们都知道 JVM 中由堆、栈、方法区所组成,其中栈内存是给谁用的呢?其实就是线程,每个线程启动后,虚拟机就会为其分配一块栈内存。

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

3.2 线程上下文切换(Thread Context Switch)

因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码

  • 线程的 cpu 时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法

当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的

  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • 线程上下文切换(Thread Context Switch) 频繁发生会影响性能

4、Java多线程API

方法名static功能说明注意事项
start()启动一个新线程,在新的线程运行 run 方法中的代码start 方法只是让线程进入就绪,里面代码不一定立刻 运行(CPU 的时间片还没分给它)。每个线程对象的 start方法只能调用一次,如果调用了多次会出现 IllegalThreadStateException
run()新线程启动后会 调用的方法如果在构造 Thread 对象时传递了 Runnable 参数,则线程启动后会调用 Runnable 中的 run 方法,否则默认不执行任何操作。但可以创建 Thread 的子类对象, 来覆盖默认行为
join()等待线程运行结 束
join(long n)等待线程运行结 束,最多等待 n 毫秒
getId()获取线程长整型 的 idid唯一
getName()获取线程名
setName(String name)修改线程名
getPriority()获取线程优先级
setPriority(int priority)修改线程优先级java中规定线程优先级是1~10 的整数,较大的优先级 能提高该线程被 CPU 调度的机率
getState()获取线程状态Java 中线程状态是用 6 个 enum 表示,分别为: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
isInterrupted()判断是否被打断不会清除打断标记
isAlive()判断线程是否还存活(线程是否运行完毕)
interrupt()打断线程如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除打断标 记 ;如果打断的正在运行的线程,则会设置打断标 记 ;park 的线程被打断,也会设置 打断标记
interrupted()判断当前线程是否被打断会清除打断标记
currentThread()获取当前正在执行的线程
sleep(long n)让当前执行的线 程休眠n毫秒, 休眠时让出 cpu 的时间片给其它 线程
yield()提示线程调度器 让出当前线程对 CPU的使用主要是为了测试和调试

5、start与run

public static void main(String[] args) {
    Thread t1 = new Thread("t1") {
        @Override
        public void run() {
            log.debug(Thread.currentThread().getName());
 
        }
    };
 //直接调用 run 是在主线程中执行了 run,没有启动新的线程
 t1.run();
 //使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码
 t1.start();
 log.debug("do other things ...");
}

6、sleep与yield

sleep

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

yield

  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
  2. 具体的实现依赖于操作系统的任务调度器

线程优先级

  1. 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  2. 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用

7、join方法

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        sleep(1);
        log.debg("t1 do something");
    });

    t1.start();
    //等待t1运行结束后,才会执行下面的代码
    t1.join();

    log.debug("main do something");
}

join原理:是调用者轮询检查线程 alive 状态

8、interrupt方法

在一个线程 T1 中如何“优雅”终止线程 T2?
这里的【优雅】指的是给 T2 一个处理后续事情的机会,即收到终止请求后不会马上终止,而是可以处理一些事情之后再终止。
可以使用interrupt() 方法,interrupt 可以打断正在执行的线程,无论这个线程是在 sleep,wait(阻塞状态),还是正常运行。

  • 打断 sleep 的线程, 会清空打断状态
  • 打断正常运行的线程, 不会清空打断状态
  • 可以使用 Thread.interrupted() 清除打断状态
  • 打断 park 线程, 不会清空打断状态,如果打断标记已经是 true, 则 park 会失效

终止模式之两阶段终止模式:因为打断 sleep 的线程, 会清空打断状态,所以会调用两次打断方法interrupt()

class TestInterrupt {
    private Thread thread;

    public void start() {
        thread = new Thread(() -> {
            while (true) {
                Thread current = Thread.currentThread();
                if (current.isInterrupted()) {
                    log.debug("处理后续事情");
                    //结束线程
                    break;
                }
                try {
                    Thread.sleep(1000);
                    log.debug("将结果保存");
                } catch (InterruptedException e) {
                    //第二次打断
                    current.interrupt();
                }
                // 执行监控操作
            }
        }, "监控线程");
        thread.start();
    }

    public void stop() {
        //第一次打断
        thread.interrupt();
    }
}

9、不推荐的方法

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

方法名static功能说明
stop()停止线程运行
suspend()挂起(暂停)线程运行
resume()恢复线程运行

使用线程对象的 stop() 方法停止线程:stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁, 其它线程将永远无法获取锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值