1 线程的创建和运行


1.1 进程与线程

  • 进程:系统资源调度与分配的基本单位
  • 线程:CPU资源调度的最小单位
  • JDK1.8的 JVM 内存模型
    • 虚拟机栈
    • 本地方法栈
    • 方法区
    • 程序计数器
  • 进程与线程的资源分配:多个线程共享进程的方法区,每个线程都有自己独立的程序计数器虚拟机栈、本地方法栈

1.2 线程创建的三种方法

  • 继承 Thread 类,重写 run(),创建对象后并没有启动多线程,调用 start() 方法后才启动
  1. /*
  2. 继承Thread类
  3. 重写run()方法-无参无返回值
  4. 创建线程 new MyThread()
  5. 启动线程 start()
  6. */
  7. ----为什么是 start() 而不是 run() 方法启动线程?
  8. start() 调用 native方法 start0() 启动多线程, run() 只是一个普通的方法
  • 实现 Runnable 接口的 run() 方法:把线程和任务分开
  1. /*
  2. 实现Runnable接口
  3. 重写run()方法-无参无返回值
  4. 创建子类对象 new RunnableTask()
  5. 创建线程 new Thread(futureTask)
  6. 启动线程 start()
  7. */
  8. ----为什么子类对象的 run() 方法会运行在线程对象中?
  9. 线程对象中声明了一个 run() 方法,子类对象的方法赋给了线程对象!
  10. if run() == null, do nothing and return.

因为在Thread中成员变量接收了传入的Runnable子类对象,所以线程能运行子类对象的run()方法。

  • 使用 Callable 方式
  1. /*
  2. 实现Callable接口
  3. 重写call()方法-无参有返回值
  4. 创建子类对象 new CallerTask()
  5. 创建异步计算结果对象 new FutureTask(callerTask)
  6. 创建线程 new Thread(futureTask)
  7. 启动线程 start()
  8. */
  9. ----提交Callable任务获取返回结果
  10. futureTask.get()

1.3 三种方法的特点

  • 继承Thread:可以通过构造方法或者set方法,方便传参,单继承局限性
  • 实现Runnable:只能使用主线程里final修饰的变量,接口可以实现多继承,更便于数据共享
  • 使用FutureTask:前两种方法都没办法拿到返回结果,FutureTask可以

1.4 线程调度方式

给线程分配CPU处理权的过程

  • 协同式线程调度
    • 线程执行时间由线程本身决定,线程完成工作后通知系统切换线程
    • 实现简单,执行时间不可控
  • 抢占式线程调度(Java线程执行方式)
    • 线程执行时间由系统决定,谁抢到谁执行(线程优先级)

1.5 线程的优先级

  • 操作系统的优先级
    • 动态优先级:正在执行中的线程,随着执行时间的延长而降低
    • 静态优先级:处于等待中的线程,随着等待时间的延长而升高
  • Java的优先级
    • 最高优先级:10
    • 最低优先级:1
    • 默认优先级:5
  • 这里的优先级只是对操作系统的建议,具有统计意义,高优先级的占用CPU时间多,低优先级的占用CPU时间少

2 线程的状态与切换


2.1 线程控制 API

  • 线程休眠 sleep ( long millions ) 与 TimeUnit.SECONDS.sleep ( long second ); 等效
  • 线程合并 join() 插队:在哪个线程执行,哪个线程就等待;哪个线程调用了 join API ,等待的就是哪个线程
  • 线程礼让 yield() 放弃当前CPU的执行权,再次加入下一次抢夺CPU执行权的队伍(抢占式线程调度)
  • 设置守护线程 setDaemon( boolean on ) 该方法需要在启动线程之前调用
    • user thread:用户线程,main线程是一个用户线程,GC线程是守护线程
    • daemon thread:守护线程,当正在运行的所有线程都是守护线程时,JVM退出
  • 线程中断 interrupt() 通过异常处理打断线程执行,实际上只抛出异常(标记),没有中断
  • 线程终止 stop() 不安全,已废弃
    • 设置一个 boolean flag 如果(if) flag 为 true 正常执行程序,否则(else) false 此时线程发生了中断,记录中断信息在 log.txt

2.2 线程状态

  • 新建状态(New),刚 new 出来,还没有 start
  • 就绪状态(Ready),执行了 start 方法,还没有获取CPU执行权
  • 执行状态(Running),有了执行权,正在执行
  • 阻塞状态(Blocked),除了没有执行权,还缺少一些必要资源
  • 死亡状态(Dead)
  • 线程的上下文切换
    • 时间片轮转算法(Round-Robin,RR)保证每个线程都能轮流执行。
    • 每次切换的时候都要保存现场与恢复现场

线程状态转换图.svg


3 线程的数据安全


3.1 多线程产数据安全的原因

  • 多线程环境
  • 数据共享
  • 非原子操作(事务的ACID)
    • 原子性(atomicity,或称不可分割性):事务中的所有操作,要么全部完成,要么全部不完成。
    • 一致性(consistency):事务开始和结束后,数据库的完整性没有被破坏,和预期结果完全一致。
    • 隔离性(isolation,又称独立性):运行多个并发事务对数据进行读写和修改,不会因为交叉执行导致数据不一致。
    • 持久性(durability):事务处理结束后,对数据库的修改是永久的,哪怕系统故障也不会丢失。

3.2 异步与同步

指的是被调用者,多线程天生异步

  • 同步:你走我不走,等你满足我的条件了我再走
  • 异步:你走你的我走我的

3.3 synchronized(关键字)

  • 同步代码块:可以是任意对象,但是要保证是同一个对象
  • 同步方法:锁对象隐含为this
  • 静态方法:锁对象的字节码文件对象

3.4 Lock(interface)

  1. try{
  2. lock.lock();
  3. //具体实现代码块
  4. }finally{
  5. lock.unlock();
  6. }

3.5 死锁

两个或者以上的线程争抢资源,互相等待的现象

  • 一般出现在 synchronized 嵌套代码块情况下
  • 解决死锁问题
    • 更改加锁顺序
    • 再加一把锁,把一个操作变成原子操作

3.6 并发与并行

  • 串行:按顺序先后执行
  • 并行:同一个时间点,同时执行
  • 并发:同一个时间段,同时执行(微观上先后,宏观上同时)

4 线程的通信


4.1 线程的等待与唤醒

  • wait():
    • 当某个线程调用共享变量的 wait() 方法时,该调用线程会被阻塞挂起,放弃对锁对象的持有
    • 直到发生下面之一才会返回:①其他线程调用了该共享对象的 notify() 或者 notifyAll() 方法,②其他线程调用了该线程的 interrupt() 方法(抛出异常)。
  1. synchronized(obj){
  2. //代码块
  3. obj.wait();
  4. obj.notifyAll();
  5. }
  • notify():唤醒当前监视器等待的单个对象,选择是随机的。
  • notifyAll():唤醒当前监视器等待的所有对象

4.2 生产者与消费者模型

  • 参考练习

5 线程池


5.1 线程池 Executors

  • 带缓存的线程池 ExecutorService newCachedThreadPool()
    • 线程数量不固定
    • 超过60s线程无任务自动死亡
    • 适用于很多短期异步小程序或者负载较轻的任务
  • 固定数量的线程池 ExecutorService newFixedThreadPool(int nThreads)
    • 线程数量固定
    • 维护一个无队列,任务发现当前无线程空闲,入队等待线程有线程空闲
    • 适用于长期任务
  • 单个线程的线程池 ExecutorService newSingleThreadPool(int nThreads)
    • 只有一个线程
    • 维护一个无队列,任务发现当前无线程空闲,入队等待线程有线程空闲
    • 适用于一个接一个执行的任务
  • 线程池的提交
    • Future<?> submit(Runnable task)
    • Future<?> submit(Callable task)
  • 关闭线程池
    • shoutdown()
    • shoutdownNow()

5.2 线程池的使用

  1. // 创建线程池
  2. // ExecutorService newFixedThreadPool(int nThreads)
  3. ExecutorService pool = Executors.newFixedThreadPool(2);
  4. // 提交任务
  5. // Future<?> submit(Runnable task)
  6. // Future<?> submit(Callable task)
  7. pool.submit(new RunnableTask);
  8. pool.submit(new RunnableTask);
  9. // 关闭线程池
  10. // shoutdown 保证现在运行的任务执行完毕,并且将在队列中等待的任务执行完
  11. pool.shoutdown();
  12. // shoutdownNow() 保证现在运行的任务执行完毕,不执行其他任务
  13. pool.shoutdownNow();

6 定时器


6.1 定时任务方法

return 方法 描述
void cancel() 终止此计时器,丢弃任何当前计划的任务。
int purge() 从该计时器的任务队列中删除所有取消的任务。
void schedule(TimerTask task, long delay) 在指定的延迟之后安排指定的任务执行。
void schedule(TimerTask task, long delay, long period) 在指定的延迟之后开始,周期性period执行指定任务。
void schedule(TimerTask task, Date time) 在指定的时间安排指定的任务执行。
void schedule(TimerTask task, Date firstTime, long period) 从指定的时间开始,周期性period执行指定任务。
void scheduleAtFixedRate(TimerTask task, long delay, long period) 在指定的延迟之后开始,重新执行固定速率的指定任务。
void scheduleAtFixedRate(TimerTask task, Date firstTime, long period) 从指定的时间开始,对指定的任务执行重复的固定速率执行 。

6.2 定时器的使用

  1. // MyTimerTask()继承了TimerTask,重写了run()方法
  2. Timer timer = new Timer();
  3. // 创建时间
  4. SimpleDateFormat sf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  5. String date = "2021-09-16 09:50:50";
  6. Date time = sf.parse(date);
  7. // 用定时器方法
  8. timer.schedule(new MyTimerTask(),time,2000);