并发编程系列学习笔记02(Java中的线程)

Java线程

创建和运行线程

  • Thread
    public static void main(String[] args) {
        
        // 创建线程对象 lambda
        Thread thread = new Thread(() -> log.info("子线程执行"));
        // 设置线程名称
        thread.setName("thread-0000");
        // 启动线程
        thread.start();

        log.info("主线程执行完毕");
    }
  • Runnable 配合 Thread
    public static void main(String[] args) {

        Runnable task = new Runnable() {
            @Override
            public void run() {
                log.info("子线程已执行");
            }
        };

        Thread thread = new Thread(task, "thread-0001");

        thread.start();

        log.info("主线程执行完毕");
    }
  • 使用Runnable小结

    • 将线程与任务解耦
    • 从而更容易与线程池等高级API配合使用
    • 脱离Thread继承体系,更灵活
  • FutureTask 配合 Thread

public static void main(String[] args) throws ExecutionException, InterruptedException {

    FutureTask<Integer> futureTask = new FutureTask<>(() -> {
        log.info("FutureTask运行");
        Thread.sleep(1000);
        return 100;
    });

    Thread thread = new Thread(futureTask, "thread-0002");

    thread.start();

    // 获得返回值,阻塞线程未执行完时,将阻塞等待返回结果
    Integer result = futureTask.get();

    log.info("获得返回值 {} ", result);

    log.info("主线程执行完毕");
}

观察多个线程运行情况

  • 交替执行
  • 执行的先后顺序不由我们控制

查看进程与线程

  • windows

    • 查看进程:tasklist
    • 杀死进程:taskkill
  • linux

    • 查看所有进程 ps -ef
    • 查看某进程下所有线程 ps -fT -p <PID>
    • 杀死进程 kill
    • 显示进程 top 按 H 切换线程
    • top -H -p <PID> 查看某个PID下所有线程
  • Java相关

    • 查看所有Java 进程 jps
    • 查看某个Java进程所有线程 状态 jstack <PID>
    • 图形界面,支持远程 jconsole

线程运行的原理

  • 栈与栈桢

    • JVM主要由堆、栈、方法区组成
    • 堆内存其实就是分给一个个线程使用的
    • 每个线程启动后,虚拟机会为其分配一块栈内存
    • 每个栈由多个栈桢(Frame)组成,栈桢对应每次方法调用所占内存
    • 一个线程同时只能有一个活动栈桢,对应正在执行的那个method
  • 线程上下文切换

    • 由于CPU会在不同线程间执行切换,切换时,操作系统将保存当前线程状态,并恢复另一个线程状态

    • 对应Java中的程序计数器概念,记录下一条JVM指令的执行地址,为线程私有

    • 线程状态信息包括

      • 程序计数器
      • 每个栈桢的信息
      • 如:局部变量、操作数栈、返回地址等
    • 频繁发生线程上下文切换影响性能

常见方法

  • start() // 启动新线程,让线程进入就绪状态
  • run() //线程被运行后执行的方法
  • join() // 等待线程运行结束
  • join(long n) // 等待结束带超时时间
  • getId() // 线程ID
  • getName() // 线程名称
  • setName(String name) //修改名称
  • getPriority() // 获取线程执行的优先级
  • setPriority(int i) // 设置优先级,1-10,值越大被CPU调度的几率越高
  • getState() // 获取状态,一共6个
  • isInterrupted() // 判断是否被打断,不会清除打断标记
  • isAlive() // 判断线程是否存活,还没运行完毕
  • interrupt() // 打断线程,将抛出异常,并清除打断标记(线程未在运行)
  • interrupted() // 判断是否被打断,同时会清除打断标记
  • currentThread() // 获取当前正在执行的线程
  • sleep(long n) // 让线程休眠 n 毫秒,让出CPU时间片
  • yield() // 提示线程调度器让出当前线程的CPU,一般用于测试或调试

start 与 run

  • 直接调用run,则会在主线程中直接执行run,不会启动新的线程
  • 调用 start 才会启动新线程,并通过线程间接执行run方法

sleep 与 yield

  • sleep会让线程状态从Running 进入 Timed Waiting,让线程睡眠,染出CPU的时间片给其他线程

  • yield 是 让线程 从Running进入Runnable,主动让出CPU对线程调度,一般用于调试

  • 线程优先级

    • 仅用于提示调度器优先调度该线程,但调度器可能忽略
    • 仅在CPU比较繁忙时才有一定效果,CPU较空闲时几乎没有作用

join

  • 用于等待指定线程执行完毕
  • 可设置超时时间,超过时间后,将不再等待

interrupt

  • 打断线程执行,并抛出异常
  • 打断调用了sleep的线程,被清空其打断标记,打断状态为false
  • 打断正在运行的线程,不会清空,打断状态为true
  • 如果线程打断标记是true,此时调用LockSupport.park()暂停线程会失效
public static void main(String[] args) {
    Thread t1 = new Thread(() -> {
        Sleeper.sleep(1);
    }, "t1");

    t1.start();

    Sleeper.sleep(0.5);
    // 打断t1线程
    t1.interrupt();
    log.info("打断线程t1后,t1的状态为 {}", t1.isInterrupted());
}
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at cn.com.ebidding.test.Sleeper.sleep(Sleeper.java:14)
	at cn.com.ebidding.test.TestInterrupt.lambda$main$0(TestInterrupt.java:15)
	at java.lang.Thread.run(Thread.java:748)
14:37:19.495 [main] INFO cn.com.ebidding.test.TestInterrupt - 打断线程t1后,t1的状态为 false

几个不推荐方法

  • stop(停止)、suspend(挂起,暂停)、resume(恢复)
  • 已过时,容易破坏同步代码块,最后造成死锁

主线程与守护线程

  • Java进程默认需等待所有线程运行结束,进程才会结束
  • 守护线程即其他非守护线程已结束后,自己会强制结束
  • 设置:t1.setDaemon(true)
  • 垃圾回收线程就是守护线程
  • Tomcat中的Acceptor 与 Poller 也是,故当Tomcat 执行 shutdown后,不会等待他们处理完当前正在处理的请求

操作系统中的线程状态(五种)

  • 初始:刚创建,还未与操作系统关联
  • 可运行:与操作系统已关联,可由CPU调度
  • 运行:已获取到了CPU时间片,正在运行中;CPU时间片用完,运行中将转换为可运行,其会导致线程上下文切换
  • 阻塞:此时线程不会用到CPU,但会导致线程上下文切换,例如BIO读写文件场景;注意阻塞与可运行加以区分,阻塞状态的线程只要不唤醒,调度器一直不会考虑调度;
  • 终止:已执行完毕生命周期接结束,不会再转换为其他状态

Java JVM中的线程状态(六种)

  • New:刚创建,还未调用start
  • Runnable:覆盖了操作系统中的可运行、运行、阻塞(IO读写)
  • Blocked:阻塞状态的细分
  • Waiting:阻塞状态的细分
  • Timed_waiting:阻塞状态的细分
  • Terminated:线程代码运行结束
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值