线程与进程

进程可以看成是线程的容器

如何创建线程

  1. 直接继承Thread类,然后重写run
  2. 实现Runnable借接口,重写任务,然后创建一个线程,然后吧这个runnable对象作为构造函数的参数,不需要继承
    组合优于继承

    这里可以使用lambada表达式

    1
    Thread thread = new Thread(() -> System.out.println(1), "t");
  3. FutrueTask实现创造线程,需要配合callable类型,有返回值
    可以返回线程的执行结果 get() 可以直接把一个线程的结果传给另一个线程

    同时可以抛出异常

观察线程同时执行

是一个轮播

  • 查询命令
    • win
      • tasklist | find java
      • taskkill
    • java
      • jps(查看java的进程)
    • Linux
      • ps -ef | grep java
      • kill pid

线程运行原理

栈和栈帧,一个线程启动之后,就会分配一个栈内存,每一个栈内,线程调用一次方法就会产生一个栈帧,一个栈只能有一个活动栈帧,对应的是当前正在执行的方法

线程上下文切换

  • CPU分配的时间片用完
  • GC
  • 有更高优先级线程需要执行
  • 线程自己调用了sleep(), yield()等方法

    线程上下文切换的时候,程序计数器会记住下一条指令的地址,以及需要记录每一个栈帧的信息比如局部变量,操作数栈,返回地址
    切换越频繁,会影响性能,不是线程数量越多越好,当线程数目大于核数,会导致频繁切换

线程中的常见方法

start() –> 启动一个新的线程,让线程进入就绪状态

只能调一次

run() –> 新的线程启动之后,就会调用run()

join() –> 等待某一个线程运行结束

join(long n) 超时时间,最多等待多少毫秒

getId(),getName(), getPriority() 优先级/set等,getState()线程状态,isAlive()是否存活

start()&run()

调用run其实是主线程调用,而不是异步

1
2
3
4
5
6
7
8
9
10
public static void main(String[] args) {
Thread t1 = new Thread("t1") {
@Override
public void run() {
System.out.println(1);
}
};

t1.run();
}

使用start()是直接线程调用,而不是主线程调用

线程状态

1
2
3
System.out.println(t1.getState());
t1.start();
System.out.println(t1.getState());

线程刚创建出来的时候,是处于NEW的状态, 然后执行start()之后的状态是runnable

sleep() & yield()

sleep()

runnable -> timed_wating

睡眠中的线程,可以被其他线程使用这个线程的interrupt()打断

醒来的线程未必会立即执行

1
2
3
while (ture) {
try (Thread.sleep(50))
}

通过sleep() 防止while true空转 导致cpu一直占用

yield()

让出当前线程的CPU使用权,当前线程从running -> runnable(就绪状态)

相当于把时间片让出来

这个方法的实现依赖与系统的任务调度器

线程优先级

数字越大表示优先级越高, 该线程会提示任务调度器, 有更多的机会分配时间片

join()

在一个线程中使用另外一个线程的join方法, 会让本线程执行到这个语句的时候, 先阻塞, 然后等待另一个线程执行完之后, 再继续运行

需要等待结果返回,就是同步,不需要等待结果返回, 就能继续运行, 就是异步

有时效的join(), 如果超过等待时间, 将不会等待这个线程, 直接往下面运行

join(Long n)

interrupt()

打断线程, 被打断之后, 线程有一个打断标记, 打断之后, 设置成真

注意: 当线程处于sleep状态的时候被打断, 会重新设置interrupt值为假,并抛出异常

isInterrupter() -> bool

对于正在sleep的线程,如果被打断之后,会抛出异常

对于正常运行的线程, 被别的线程打断之后, 被打断值为真, 由程序自己判断是否需要打断, 可以自己通过isinterrupted() 判断逻辑

关于interrupt的两阶段终止设计模式

在线程1中如何优雅的终止另外一个线程2, 需要给线程2一个料理后事的机会(释放锁等动作)

  • 错误思路

    • 直接使用stop(), 强制杀死线程
    • 但是如果线程持有锁, 锁将不能释放,不够合理

  • 两阶段终止模式

    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      public class 两阶段终止模式 {
      public static void main(String[] args) throws InterruptedException {
      TwoPhaseTermination twoPhaseTermination = new TwoPhaseTermination();

      twoPhaseTermination.start();

      Thread.sleep(3500);

      twoPhaseTermination.stop();
      }
      }

      class TwoPhaseTermination {
      private Thread monitor; // 监控线程

      // 启动监控线程
      public void start() {
      monitor = new Thread(() -> {
      while (true) {
      // 先拿到当前线程的对象
      Thread current = Thread.currentThread();

      if (current.isInterrupted()) {
      System.out.println("料理后事");
      break;
      }

      try {
      Thread.sleep(1000); // 情况1: 此时被打断会抛出异常, 此时会把打断标记重新设置成假

      System.out.println("执行监控记录"); // 情况2: 这个情况被打断不会抛出异常
      } catch (InterruptedException e) {

      // 重新设置打断标记
      current.interrupt();
      System.out.println(e.toString());
      //throw new RuntimeException(e);
      }
      }
      });

      monitor.start();
      }

      // 停止监控线程
      public void stop() {
      monitor.interrupt();
      }
      }

打断park()状态线程

LockSupport.park(); // 锁住了

当打断标记为真的时候, park()就会生效

主线程与守护线程

只要有一个线程没结束, 进程就不会结束

守护线程

1
t.setDaemon(true); // 设置t为守护线程

当一个线程为守护线程的时,他守护的线程结束之后, 无论守护线程是否结束, 都会立即结束

线程的状态

  • 五种状态的说法
    初始 -> 可运行状态 -> 运行状态 -> 终止状态
    | |
    阻塞状态 <–
  • 六种状态

    根据state的枚举类划分

NEW RUNNABLE BLOCKED WAITING TIMED_WAITING TERMINATED

小结