并发基础笔记-(线程基础)

目录


  1. 线程基础
  2. 同步及实现
  3. 阻塞队列
  4. 线程安全集合
  5. Callable与Future
  6. 线程池-执行器
  7. 同步器-同步队列

线程 在较低层次扩展了 多任务 的概念
线程与进程的本质区别 每个进程有一套自己的变量, 而线程共享数据(线程间通信更有效, 容易)

创建

// 函数式接口 Runnable 实例
Runnable r = () -> { /* task code */ };
// 由 Runnable创建 Thread 对象
Thread t = new Thread(r);
// 启动该线程
// t.run()只是调用run方法,不会启动新线程
t.start();
复制代码

中断线程

线程终止情况

  1. run方法执行结束, 并由return返回时
  2. 出现方法中没有捕获的异常时
  3. 早期版本中可以调用stop(), 后弃用

没有可以强制线程终止的方法 可以通过 interrupt() 请求终止线程

每个线程都有 中断状态 的boolean标志, 线程调用实例方法interrupt()将该标志置位(true), 应当时不时检测线程是否被置位

// Thread.currentThread()获取当前线程, isInterrupted()是否中断
while(!Thread.currentThread().isInterrupted() /*&& more work to do*/){
    // do more work
}
复制代码

线程被阻塞时, 调用interrupt(), 阻塞调用被InterruptedException中断, 且线程中断状态被重置(false), 因此, 此时检测中断状态isInterrupted()也就没有意义

方法 Thread.interrupted()清除中断状态(即: 重置为false)
中断一个线程是引起它的注意, 被中断的线程可以决定如何响应中断 如: 处理完异常之后继续执行 finally..., 不过普遍的是做为终止请求

注意
不要不处理InterruptedException

// 错误示范
void testTask(){
    ...
    try{Thread.sleep(delay);}
    catch(IntrrrputedException e){}
    ...
}
// 正确方式1
void testTask1(){
    ...
    try{Thread.sleep(delay);}
    // 中断状态置位, 以便调用者检测
    catch(InterruptedException e){ Thread.currentThread().interrupt(); }
    ...
}
// 正确方式2
// 抛出以便调用者捕获
void testTask1() throws InterruptedException{
    ...
    Thread.sleep(delay);
    ...
}
复制代码

线程状态
6种状态(t.getState()获取当前状态):

  • New(新创建)

    使用new创建线程时, 此时并未开始运行其中的代码

  • Runanble(可运行)

    调用start()进入该状态, 该状态的线程 可能在运行 也可能 没有运行, 线程开始运行未必始终运行, 运行中断是提供给其他线程运行机会,

    • 抢占式调度在线程时间片用完时剥夺运行权并根据优先级选择另一个线程
    • 协作式调度(手机), 调用yield方法(从运行中到就绪) 或被阻塞或等待时, 线程失去控制权
  • Blocked(被阻塞)

    该状态下不活动不运行代码并消耗最少资源, 直到被重新激活

    • 试图获得一个内部对象锁(非JUC库中的锁), 而该锁被其他线程持有, 进入阻塞状态. 当所有其他线程释放该锁, 且调度器允许该线程持有此锁, 进入可运行状态.
  • Waiting(等待)

    与阻塞状态有很大不同

    • 当线程等待另一个线程通知调度器一个条件时, 自己进入等待状态
    • 调用Object.waitThread.join, 或等待JUC库中的Lock或Condition时
  • Timed waiting(计时等待)

    因带有超时参数方法进入计时等待状态, 状态持续到超时期满或接收适当通知

    • Thread.sleep Object.wait Thread.join Lock.tryLockCondition.await计时版
  • Terminated(被终止)

    见上节

当一个线程被阻塞或等待或终止时, 另一个线程被调度为运行状态. 当另一个线程等待期满或成功获得锁, 调度器比较其与当前运行线程的优先级, 若高则从当前运行线程中挑选一个, 剥夺运行权, 运行一个新线程

线程属性

线程优先级

缺省线程继承其父类线程优先级, setPriority(int newPriority)提高或降低任何一个线程优先级. 优先级高度依赖运行系统, 如Linux下所有线程优先级相同.

应注意, 如果有几个高优先级未进入非活动状态, 每次选择运行新线程时都会选择高优先级, 可能会导致低优先级线程饿死.

static void yield( ), 让当前执行前线程处于让步状态. 若有其他线程至少高于该线程优先级, 这些线程接下来会被调度.

守护线程

线程启动之前调用t.setDaemon(true)

唯一用途是为其他线程提供服务. 当只剩下守护线程, 虚拟机则退出, 因为没有必要继续运行程序, 因此守护线程不能去访问固有资源, 如文件, 数据库, 因为它会在任何时候甚至一个操作中间中断.

未捕获异常处理器

run方法不能抛出任何受查异常, 但是非受查异常会导致线程终止. 线程死亡前异常会被传递到用于未捕获异常的处理器.

该处理器必须实现Tread.UncaughtExceptionHandler(内部接口)

  • Thread.setDefaultUncaughtExceptionHandler(...)为所有线程安装默认处理器
  • t.setUncaughtExceptionHandler(...)为该线程设置处理器
// Thread.java
// 不设置默认处理器返回null
public static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler(){
    return defaultUncaughtExceptionHandler;
}

// 独立线程不设置则返回该线程的ThreadGroup对象
public UncaughtExceptionHandler getUncaughtExceptionHandler() {
    return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group;
}

// ThreadGroup.java 不建议使用线程组
public void uncaughtException(Thread t, Throwable e) {
    if (parent != null) { // 调用父线程
        parent.uncaughtException(t, e);
    } else {
        Thread.UncaughtExceptionHandler ueh = 
            Thread.getDefaultUncaughtExceptionHandler();
        if (ueh != null) { // 调用默认的
            ueh.uncaughtException(t, e);
        } else if (!(e instanceof ThreadDeath)) { // 输出栈轨迹
            System.err.print("Exception in thread \""
                             + t.getName() + "\" ");
            e.printStackTrace(System.err);
        }
    }
}
复制代码

参考: 《Java核心技术卷1》

猜你喜欢

转载自juejin.im/post/5cd665716fb9a03214377dfc