Java线程 (2)

1. 创建和运行线程

1.1 Thread

public static void main(String[] args) {
        // 创建线程对象
        Thread thread = new Thread(){
            @Override
            public void run() {
                // 要执行的任务
                System.out.println(Thread.currentThread().getName()+"==>"+"running");
            }
        };
        //给线程命名
        thread.setName("t1");
        // 启动线程
        thread.start();

        System.out.println(Thread.currentThread().getName()+"==>"+"running");
    }

1.2 Runnable

public static void main(String[] args) {
        // 创建任务对象
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+"===>"+"running");
            }
        };
        //参数1 是任务对象; 参数2 是线程名字
        Thread thread = new Thread(runnable,"t1");
        thread.start();
        System.out.println(Thread.currentThread().getName()+"===>"+"running");
    }

Thread 和 Runnable的区别

Runnable接口是一个线程任务类最终执行还是要通过Thread类的start方法

1.3 Callable

FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况

public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建任务对象
        FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {

                System.out.println(Thread.currentThread().getName()+"===>running");
                Thread.sleep(1000);
                return 100;
            }
        });
        // 参数1 是任务对象; 参数2 是线程名字
        Thread thread = new Thread(task,"t1");
        thread.start();
        // 主线程阻塞,同步等待 task 执行完毕的结果
        System.out.println(Thread.currentThread().getName()+"===>"+task.get());
    }

执行结果:

t1===>running
main===>100

2. 查看线程的方法

windows

  • 任务管理器可以查看进程和线程数,也可以用来杀死进程
  • tasklist 查看进程, 可以在后面跟上| findStr 进行过滤
  • taskkill 杀死进程(如: taskkill /F /PID 1002)/F 强制删除

linux

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

Java

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

3. 线程运行原理

栈与栈帧

Java Virtual Machine Stacks (Java 虚拟机栈)

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

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

总结:每一个线程对应一个栈内存,而线程中方法则对应栈帧。

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

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

  • 线程的 cpu 时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
  • 当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
  • 状态包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等
  • Context Switch 频繁发生会影响性能

4 线程常见方法

4.1 start vs run

  • 直接调用 run 是在主线程中执行了 run,没有启动新的线程
  • 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

4.2 sleep vs yield

sleep

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

yield

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

4.3 线程优先级

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

4.4 join

阻塞等待线程执行完毕,底层是使用wait方法

4.5 interrupt

  • sleep、wait、join都是阻塞状态,使用interrupt打断处于阻塞状态的线程时会使程序抛出异常并清空打断标记,也就是将打断标记置为false。
  • 使用interrupt打断正常运行的线程时只会将打断标记置为true,并不会真正的停止线程,我们可以通过判断线程的isInterrupted()方法手动安全的停止线程。

4.6 结束线程之两阶段终止模式

错误思路

  • 使用线程对象的 stop() 方法停止线程,stop 方法会真正杀死线程,如果这时线程锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
  • 使用 System.exit(int) 方法停止线程,目的仅是停止一个线程,但这种做法会让整个程序都停止

两阶段终止模式

代码模拟:

public class Test5 {


    public static void main(String[] args) throws InterruptedException {
        TPTInterrupt tptInterrupt = new TPTInterrupt();
        System.out.println("开启监控线程");
        tptInterrupt.start();

        Thread.sleep(5000);

        System.out.println("关闭监控线程");

        tptInterrupt.stop();



    }

   static class TPTInterrupt{

        Thread monitor;

        public void start(){
            monitor = new Thread(()->{
                while (true){
                    if(monitor.isInterrupted()){
                        System.out.println("料理后事");
                        break;
                    }

                    try {
                        Thread.sleep(1000); //case1: 打断休眠中的线程
                        System.out.println("执行监控线程"); // case2: 打断运行中的线程
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        monitor.interrupt();
                    }
                }
            },"监控线程");
            monitor.start();
        }


        public void stop(){
            monitor.interrupt();
        }
    }
}

执行结果:

开启监控线程
执行监控线程
执行监控线程
执行监控线程
执行监控线程
关闭监控线程
料理后事
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.anchor.test.Test5$TPTInterrupt.lambda$start$0(Test5.java:34)
	at java.lang.Thread.run(Thread.java:750)

Process finished with exit code 0

LockSupport.park()休眠线程,LockSupport.unpark()唤醒线程,两个方法配合使用。也可以通过LockSupport.parkNanos()指定休眠时间后,自动唤醒。 LockSupport.park()不会释放monitor锁。 线程被打断,LockSupport.park()不会抛出异常,也不会吞噬掉interrupt的状态,调用者可以获取interrupt状态,自行进行判断,线程是由于什么原因被唤醒了。 LockSupport.park()会是线程进入WAITING状态,而LockSupport.parkNanos(long nanos) 会进入TIMED_WAITING状态。

如果是打断LockSupport.park(),park内部通过判断是否被打断来决定是暂停线程,使用isInterrupted()不会清除打断标记,则将无法通过park方法再次休眠线程。 可使用静态方法Thread.interruped(),此方法会清除打断标记。

4.7 主线程与守护线程

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。例如:java中的垃圾回收线程就是一种守护线程。

5 线程状态

5.1 五种状态

从操作系统层面描述的线程状态

操作系统五种状态

  • 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
  • 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
  • 【运行状态】指获取了 CPU 时间片运行中的状态
    • 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  • 【阻塞状态】
    • 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
    • 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
    • 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
  • 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

5.2 六种状态

从java api 层面的描述的线程状态,主要对应Thread.State 枚举

java api 中的六种状态

  • NEW 线程刚被创建,但是还没有调用 start() 方法
  • RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【可运行状态】、【运行状态】和【阻塞状态】(由于BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行)
  • BLOCKED , WAITING , TIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节详述
  • TERMINATED 当线程代码运行结束

用Java代码模拟线程的不同状态:

import java.io.IOException;

public class TestState {
    public static void main(String[] args) throws IOException {
        Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "===>running...");
            }
        };

        Thread t2 = new Thread("t2") {
            @Override
            public void run() {
                while (true) { // runnable

                }
            }
        };
        t2.start();

        Thread t3 = new Thread("t3") {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "===>running...");
            }
        };
        t3.start();

        Thread t4 = new Thread("t4") {
            @Override
            public void run() {
                synchronized (TestState.class) {
                    try {
                        Thread.sleep(1000000); // timed_waiting
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t4.start();

        Thread t5 = new Thread("t5") {
            @Override
            public void run() {
                try {
                    t2.join(); // waiting
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t5.start();

        Thread t6 = new Thread("t6") {
            @Override
            public void run() {
                synchronized (TestState.class) { // blocked
                    try {
                        Thread.sleep(1000000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t6.start();

        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1 state " + t1.getState()); //NEW
        System.out.println("t2 state " + t2.getState()); // RUNNABLE
        System.out.println("t3 state " + t3.getState());  // TERMINATED
        System.out.println("t4 state " + t4.getState());  // TIMED_WAITING
        System.out.println("t5 state " + t5.getState()); // WAITING
        System.out.println("t6 state " + t6.getState()); // BLOCKED
        System.in.read();
    }
}
end

评论