Day02 【学习日记】并发编程-Java线程

119 阅读4分钟

1.创建线程的三种方式

  1. 方式一:直接使用Thread
 //创建线程对象
 Thread t = new Thread(){
     @Override
     public void  run(){
          // 要执行的任务
     }
 };
 t.start(); 
  1. 方式二:使用Runnable配合Thread
Runnable runnable = new Runnable(){
    @Override
    public void run(){
        // 要执行的任务
    }
};
// 创建线程对象
Thread t = new Thread(runnable);
t.start();
  1. 方式三:使用FutureTask配合Thread
// FutureTask能够接受Callable类型的参数,用来处理有返回值的情况
FutureTask<String> futureTask = new FutureTask<>(new Callable<String>{
    // 要执行的任务
    return "返回值";
});
Thread t = new Thread(futureTask);
t.start();
// 主线程阻塞,同步等待 futureTask执行完毕的结果
futureTask.get();

2.观察多个线程同时运行

  • 交替运行
  • 哪个线程优先运行不由我们控制,是由操作系统去控制

3.查看java线程、进程运行的方法

  • Windows
    • 任务管理器可以查看进程和线程运行状态,也可以通过“结束任务”来杀死进程或线程
    • 打开cmd,输入“tasklist”查看进程列表
    • 打开cmd,输入“taskkill”杀死进程
  • linux
    • ps -fe查看所有进程
    • ps -fT -p <PID>查看某个进程的所有线程
    • kill 杀死进程
    • top实时查看进程运行状态
    • top -H -P <PID>实时查看某个进程的所有线程运行状态
  • java
    • jps查看所有的java进程
    • jstack<PID>查看某个java进程的所有线程信息
    • jconsole来查看某个进程中线程的运行状态(图形界面)

4.原理之线程运行

  1. Java Virtual Machine Stacks(Jva虚拟机栈)
    • 栈与栈帧 ,JVM由堆、方法区、栈所组成。每个线程启动后,虚拟机会为其分配一块栈内存
    • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所调用的内存
    • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法
  2. 线程上下文切换(Thread Context Switch)
    • 因为有一些原因导致CPU不再执行当前线程,转而执行另外一个线程的代码
    • 线程的CPU时间片用完
    • 垃圾回收
    • 有更高优先级的线程运行
    • 调用sleep、join、wait、park、synchronized、lock等方法
    • 当Context Switch发生时,需要操作系统保存当前线程的状态,并恢复另一个线程的状态,java中对应的概念就是程序计数器,它的作用是记住下一条JVM指令的执行地址,是线程私有的
    • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,栈帧里面包括局部变量,操作数栈,返回地址等
    • Context Switch频繁发生会影响性能

5.start和run

  • 直接调用run方法,实际上还是主线程在执行任务,没有启动新的线程
  • 使用start是使用了新的线程,通过新的线程简介执行run方法

6.yield和sleep

  • sleep
    • 调用sleep会让当前线程从Running进入Timed Waiting状态(阻塞)
    • 其他线程可以使用interrupt方法打断正在睡眠的线程,这是sleep方法会抛出InterruptedException
    • 睡眠结束后的线程未必会立刻得到执行
    • 建议使用TimeUnit的sleep代替Thread的Sleep来获取更好的可读性
  • yield
    • 调用yield的线程会从Running进入Runnable状体(就绪),然后调度执行其他线程
    • 具体实现依赖于操作系统的任务调度器,当线比较空闲时基本上没有什么直观的效果
  • 线程优先级
    • 线程优先级会提示调度器优先调度该线程,但仅仅只是一个提示,调度器可以忽略这个提示
    • 在CPU比较忙时,那么优先级高的线程可以获得更多的时间片,但CPU空闲时,几乎没有什么效果

7.join方法详解

static int a = 0;
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        a = 10;
    }, "t1");
    t1.start();
    // a最终输出0
    System.out.println(a);
}
  • 分析
    • 因为主线程和线程“t1”是同时执行的,所以主线程并不会等待“t1”线程执行完才输出结果
  • 解决办法
    • 使用“Thread.join()”,放在“t1.start()”后面即可
    • 使用Sleep,不过不建议这么使用,因为我们无法预知“t1”线程究竟要执行多久
  • 应用
    • 需要等待结果返回,才能继续运行就是同步
    • 不需要等待结果返回还能继续运行就是异步