03月12, 2017

线程简介

归纳总结多线程并发编程相关基础概念。

生命周期

在给定的某个时刻内,线程只能处于生命周期下6个状态之一。

状态 说明
NEW 线程被初始化,还未调用start()方法
RUNNABLE 运行状态,就绪和运行中状态统称为“运行中”
BLOCKED 阻塞状态,线程被锁阻塞
WAITING 等待状态,表示该线程需要等待其它线程的动作(通知、中断)
WAITING 超时等待状态,在指定时间内自行返回的等待状态
TERMINATED 终止状态,线程执行完毕

Daemon守护线程

守护线程为支持型线程,在程序后台运作。当一个JVM中不存在非Daemon线程时将退出。Daemon属性要在线程启动前设置

// 将线程设置为守护线程
Thread.setDaemon(true);

线程中断

  • 线程中断表示该线程是否被其它线程中断操作,实质是对interrupted标志位更改。
  • 如果该线程处于终结状态那么即使在运行过程中被中断过isInterrupted()会返回false。
  • 在Java会抛出InterruptedException异常时,会先对中断标识位进行复位再抛出异常。

举个例子:

  • 睡眠线程 Thread.sleep(long mills)
   public static class SleepRunner implements Runnable {
        @Override
        public void run() {
            while (true) {
                // 睡眠10s
                CommonUtil.sleep(10);
            }
        }
    }
  • 繁忙线程:
public static class BusyRunner implements Runnable {
    @Override
    public void run() {
        while (true) {
            // 空循环造成线程繁忙
        }
    }
}
  • 分别启动后中断线程,并查看中断标识isInterrupted()

    SleepRunner interrupted status is: false Thread[SleepRunner] Sleep Exception: sleep interrupted BusyRunner interrupted status is: true

SleepRunner因为调用Thread.sleep()方法被interrupted时抛出 InterruptedException从而重置了中断标识位,最终isInterrupted()返回false

过期的控制方法

方法 说明 (线程资源包括锁)
suspend() 线程挂起,不会释放线程资源
resume() 线程继续
stop() 终结线程,不保证线程资源正确释放

因为这些方法对线程资源的不可靠控制所以被Java标记为过时方法,且被等待/通知机制替代。

volatile关键字

  • JMM允许线程保存共享内存数据的副本以提升程序的运行效率。所以在多线程环境下线程中的数据不一定是共享内存中的最新值。
  • volatile用来保证变量的实时可见性。任何线程访问该变量需要从共享内存中获取,而对它的改变必须同步刷新回共享内存。但是过多的使用volatile变量会影响程序的执行效率

synchronized关键字

  • 该关键字可以修饰方法或者构成一个方法块,被修饰的方法在同一个时刻只能有一个线程处于同步块中,以此保证了多线程环境下对变量访问的可见性和排他性。
  • synchronized关键字本质是对一个对象的监视器(monitor)进行获取,其过程是排他的。当一个线程进入同步方法时需要先获取synchronized修饰对象的monitor,在没有获取到时,该线程将会被阻塞(BLOCKED状态)进入同步队列。当该对象的释放了锁(即对象monitor)时,会唤醒在同步队列中的所有线程使其重新争夺锁的控制权。

等待/通知机制

等待/通知机制是用来解决为了满足多线程间通信而造成的实时性与执行效率间矛盾的办法。

方法 描述
notify() 通知一个在对象上等待的线程,在其获取该线程的对象锁后使用wait()方法返回。
notifyAll() 通知所有在该对象上等待的线程
wait() 调用该方法线程进入WAITING状态,并释放持有的对象锁。直到别的线程的通知被中断才会返回。
wait(long) 超时等待一段时间(long mills),超时后返回。
wait(long,int) 更加细粒度的时间控制

以上方法被定义在java.lang,Object对象上,所以所有对象均可使用。

举个例子:

  • 等待线程 `java static class Wait implements Runnable {
      public void run() {
          // 加锁, 持有lock的Monitor
          synchronized (lock) {
              // 当条件不满足时,waitting, 同时释放了lock的锁
              while (flag) {
                  try {
                      System.out.println(Thread.currentThread() + " flag is true. wait @ " + new SimpleDateFormat(" HH: mm: ss").format(new Date()));
                      lock.wait();
                  } catch (InterruptedException e) {
                  }
              } // 条件满足时,执行结束
              System.out.println(Thread.currentThread() + " flag is false. running @ " + new SimpleDateFormat(" HH: mm: ss").format(new Date()));
          }
      }
    
    }

- 通知线程
```java
static class Notify implements Runnable {
    public void run() { // 加锁, 拥有lock的Monitor
        synchronized (lock) { // 获取lock的锁,然后进行通知, 通知时不会释放锁, // 直到当前线程释放了lock后, WaitThread才能从wait方法中返回

            System.out.println(Thread.currentThread() + " hold lock. notify @ " + new SimpleDateFormat(" HH: mm: ss").format(new Date()));
            lock.notifyAll();
            flag = false;
            CommonUtil.sleep(5);
        } // 再次加锁
        synchronized (lock) {
            System.out.println(Thread.currentThread() + " hold lock again. sleep @ " + new SimpleDateFormat(" HH: mm: ss").format(new Date()));
            CommonUtil.sleep(5);
        }
    }
}
  • 先后启动等待与通知线程
    public static void main(String[] args) throws Exception {
      Thread waitThread = new Thread(new Wait(), "WaitThread");
      waitThread.start();
      // 让WaitThread充分执行
      CommonUtil.sleep(1);
      Thread notifyThread = new Thread(new Notify(), "NotifyThread");
      notifyThread.start();
    }
    
  • 输出结果

    Thread[WaitThread,5,main] flag is true. wait @ 09: 39: 33 Thread[NotifyThread,5,main] hold lock. notify @ 09: 39: 34 Thread[NotifyThread,5,main] hold lock again. sleep @ 09: 39: 39 Thread[WaitThread,5,main] flag is false. running @ 09: 39: 44

当等待线程lock.wait()时,线程交出锁(monitor,对象的持有权),此时通知线程得到monitor开始执行代码。当通知线程lock.notifyAll();时,等待线程被唤醒但是通知线程还未释放锁(WAITING > BLOCKED/等待队列 > 同步队列),所以最终在通知线程流程处理完后释放了锁,等待线程持有锁后从wait()返回走完剩余流程。

Thread.join()

方法 说明
join() 该线程终止后,从thread.join()返回。
join(long mills) 如果该线程在给定时间未终止,从thread.join()返回。

举个例子:

// 多米乐类,每一个线程调用前一个线程的join(),在前一个线程终止后执行
static class Domino implements Runnable{
    private  Thread thread;

    public Domino(Thread thread){
        this.thread = thread;
    }

    @Override
    public void run() {
        try {
            this.thread.join();
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }
        System.out.println(Thread.currentThread().getName() + " terminate.");
    }
}
  • 主线程
   public static void main(String[] args) {
        Thread previous = Thread.currentThread();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Domino(previous),String.valueOf(i));
            thread.start();
            previous = thread;
        }
        CommonUtil.sleep(5);
        System.out.println( Thread.currentThread().getName() + " terminate");
    }
  • 执行结果

    main terminate 0 terminate. 1 terminate. 2 terminate. 3 terminate. 4 terminate. 5 terminate. 6 terminate. 7 terminate. 8 terminate. 9 terminate.

当主线程等待5秒后,每一个线程被自己的前置线程唤醒,输出当前线程名。 join()其本质是利用了线程等待/通知机制实现对线程通信的控制。

源码:

public final synchronized void join(long var1) throws InterruptedException {
    long var3 = System.currentTimeMillis();
    long var5 = 0L;
    if (var1 < 0L) {
        throw new IllegalArgumentException("timeout value is negative");
    } else {
        if (var1 == 0L) {
            // 当标记为true后使线程等待
            while(this.isAlive()) {
                this.wait(0L);
            }
        } else {
          ...
        }

    }
}

ThreadLocal 线程变量

一种以线程对象为key,任意对象为值的存储结构。 举个例子

 public class Profiler {
    // 在get()时会自动初始化变量
    private static final ThreadLocal<Long> TIME_THREADLOCAL = ThreadLocal.withInitial(() ->
            System.currentTimeMillis()
    );

    public static void begin() {
        TIME_THREADLOCAL.set(System.currentTimeMillis());
    }

    public static long end() {
        return System.currentTimeMillis() - TIME_THREADLOCAL.get();
    }

    public static void main(String[] args) {
        begin();
        CommonUtil.sleep(2);
        System.out.println("spend time: " + end() + "mills");
    }

}

该方法利用ThreadLocal绑定线程的特性可配合AOP实现方法级别的性能测试。

本文链接:https://check321.net/post/thread_intro.html

-- EOF --

Comments

请在后台配置评论类型和相关的值。