少女祈祷中...

1.概念

  • 一个程序可以包括多个子任务,可串/并行
  • 每个子任务可以称为一个线程
  • 如果一个子任务阻塞,程序可以将CPU调度另外一个子任务进行工作。这样CPU还是保留在本程序中,而不是被调度到别的程序(进程)去。这样,提高本程序所获得CPU时间和利用率

2.多线程实现

多线程创建

  • java.lang.Thread
    • 线程继承Thread类,实现run方法
    1
    2
    3
    4
    5
    public class Thread1 extends Thread {
    public void run() {
    System.out.println("run");
    }
    }
  • java.lang.Runnable接口
    • 线程实现Runnable接口,实现run方法
    1
    2
    3
    4
    5
    public class Thread2 implements Runnable {
    public void run() {
    System.out.println("run");
    }
    }

多线程启动

  • start方法,会自动以新进程调用run方法
1
2
3
4
5
//继承Thread类
new Thread1().start();

//实现Runnable接口,必须包装在Thread类中才能启动
new Thread(new Thread2()).start();
  • 直接调用run方法,将变成串行执行
  • 同一个线程,多次start会报错,只执行第一次start方法
  • 多个线程启动,其启动的先后顺序是随机的
  • 线程无需关闭,只要其run方法执行结束后,自动关闭
  • main函数(线程)可能早于新线程结束,整个程序并不终止
  • 整个程序终止是等所有的线程都终止(包括main函数线程)

实现对比

  • Thread vs Runnable
    • Thread占据了父类的名额,不如Runnable方便
    • Thread 类实现Runnable
    • Runnable启动时需要Thread类的支持
    • Runnable 更容易实现多线程中资源共享
  • 结论:建议实现Runnable接口来完成多线程

3.多线程信息共享

  • 通过共享变量在多个线程中共享消息
    • 如果继承了Thread类,只能使用static变量
    • 如果实现了Runnable接口,可以使用同一个成员变量

信息共享问题

  • 工作缓存副本
    • 解决方法
      • 采用volatile关键字修饰变量
      1
      private volatile boolean flag = false;
      • 保证不同线程对共享变量操作时的可见性
  • 关键步骤缺乏加锁限制
    • 解决方法:关键步骤加锁限制
      • 互斥:某一个线程运行一个代码段(关键区),其他线程不能同时运行这个代码段
      • 同步:多个线程的运行,必须按照某一种规定的先后顺序来运行
      • 互斥是同步的一种特例
    • 互斥的关键字是synchronized
      • synchronized代码块/函数,只能一个线程进入
      • synchronized加大性能负担,但是使用简便
      1
      2
      3
      4
      5
      6
      7
      8
      9
      public synchronized void sale() {
      //TODO
      }
      //等同于
      public void sale() {
      synchronized(this) {
      //TODO
      }
      }

4.多线程管理

线程状态

  • NEW 刚创建(new)

  • RUNNABLE 就绪态(start)

  • RUNNING 运行中(run)

  • BLOCK 阻塞(sleep)

  • TERMINATED 结束

  • Thread的部分API已经废弃

    • 暂停和恢复 suspend/resume
    • 消亡 stop/destroy
  • 线程阻塞/和唤醒

    • sleep,时间一到,自己会醒来
    • wait/notify/notifyAll,等待,需要别人来唤醒
    • join,等待另外一个线程结束
    • interrupt,向另外一个线程发送中断信号,该线程收到信号,会触发InterruptedException(可解除阻塞),并进行下一步处理
  • 线程被动地暂停和终止

    • 依靠别的线程来拯救自己
    • 没有及时释放资源
  • 线程主动暂停和终止

    • 定期监测共享变量
    • 如果需要暂停或者终止,先释放资源,再主动动作
    • 暂停:Thread.sleep(),休眠
    • 终止:run方法结束,线程终止
  • 多线程死锁

    • 每个线程互相持有别人需要的锁
    • 预防死锁,对资源进行等级排序
  • 守护(后台)线程

    • 普通线程的结束,是run方法运行结束
    • 守护线程的结束,是run方法运行结束,或main函数结束
    • 守护线程永远不要访问资源,如文件或数据库等
    1
    2
    3
    Thread1 t = new Thread1();
    t.setDaemon(true);
    t.start();
  • 线程查看工具 jvisualvm

线程组管理

  • 线程组ThreadGroup
    • 线程的集合
    • 树形结构,大线程组可以包括小线程组
    • 可以通过enumerate方法遍历组内的线程,执行操作
    • 能够有效管理多个线程,但是管理效率低
    • 任务分配和执行过程高度耦合
    • 重复创建线程、关闭线程操作,无法重用线程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 创建线程组
ThreadGroup threadGroup = new ThreadGroup("Searcher");

//创建线程
Searcher searchTask=new Searcher();
Thread thread=new Thread(threadGroup, searchTask);
//public class Searcher implements Runnable{}

//返回线程组中还处于active的线程数(估计数)
System.out.println(threadGroup.activeCount());

//将线程组中active的线程拷贝到数组中
Thread[] threads=new Thread[threadGroup.activeCount()];
threadGroup.enumerate(threads);
for (int i=0; i<threadGroup.activeCount(); i++) {
System.out.printf("Thread %s: %s\n",threads[i].getName(),threads[i].getState());
}

//打印线程组中所有的线程信息
threadGroup.list();

//对线程组中的所有线程发出interrupt信号
threadGroup.interrupt();