线程基础
线程是一个程序内部的顺序控制流。
- 进程:有独立的代码和数据空间(进程上下文),切换的开销大
- 线程:轻量的进程,同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小
- 多进程:操作系统同时运行多个任务(程序)
- 多线程:同一应用程序中有多个顺序流同时执行
线程的概念模型
- 虚拟的CPU:由java.lang.Thread类封装和虚拟;
- 代码:传递给Thread类对象执行
- 数据:传递给Thread类对象处理
创建线程
java.lang.Thread类
run()方法:线程体
方法1:使用Runnable接口创建线程
- 定义类实现Runnable接口,重写run()方法;
- 创建Thread对象,封装Runnable接口实现类对象;
- 调用Thread对象的start()方法。
public class TestThread {
public static void main(String[] args) {
MyRunner r = new MyRunner();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
class MyRunner implements Runnable {
@Override
public void run() {
for (int i = 0; i < 30; i++) {
String s = Thread.currentThread().getName();
System.out.println(s + ":" + i);
}
}
}
多线程共享代码
多线程之间可以共享代码和数据MyRunner r = new MyRunner(); Thread t1 = new Thread(r); Thread t2 = new Thread(r);
线程 虚拟CPU 代码 数据 t1 Thread类对象t1 MyRunner类的run方法 MyRunner类对象r t2 Thread类对象t2 MyRunner类的run方法 MyRunner类对象r
方法2:直接继承Thread类创建线程
- 继承Thread类,重写run()方法
- 创建Thread子类对象
- 调用对象start()方法
这里写代码片
两种方式比较:
使用Runnable接口创建线程:
- CPU/代码/数据分开,模型清晰
- 线程体run()方法所在类还可以继承其他类
- 有利于保持程序风格一致性
直接继承Thread类创建线程:
- Thread子类不能在继承其他类
- 编写简单,run()方法当前对象就是线程对象,可直接操纵
后台线程/守护线程/精灵线程
- 后台处理(Background Processing)
- 后台线程/守护线程/精灵线程(Background Thread/Daemon Thread):如垃圾回收线程、定时器线程
- 用户线程(User Thread)
- 主线程(Main Thread)
- 子线程(Sub Thread)
方法:
boolean isDaemon()
void setDaemon(Boolean on)
注意:当一个应用程序的所有用户线程都执行结束后,JVM都会停止后台线程的执行(当可能存在延时)。
GUI线程
常见GUI线程
- AWT-Windows
线程:操作系统获取底层事件信息
- AWT-EventQueue-n
线程:GUI事件处理队列,从AWT-Windows
线程获取事件消息
- AWT-Shutdown
线程:负责关闭已经打开的抽象窗口工具,释放GUI所有占用的资源,等其他线程退出后才执行
其他非GUI线程:
- DestoryJavaVM
:释放所占用的系统资源,卸载JVM,清除内存class和内存空间。在主线程main执行结束后被拉起
线程控制
线程生命周期
- 新建:start()进入就绪状态
- 就绪:线程调度器调度后进入运行状态
- 运行:阻塞事件(如IO读取)进入阻塞状态;run()执行完毕进入终止状态
- 阻塞:阻塞解除进入就绪状态
- 终止:终止后不能重新start()
线程优先级
- 范围1~10,缺省为5
- 子线程优先级默认与父线程相同
方法:
int getPriority()
void setPriority(int newPriority)
静态常量:
Thread.MIN_PRIORITY=1
Thread.MAX_PRIORITY=10
Thread.NORM_PRIORITY=5
线程控制中,不能依赖线程优先级,来决定线程执行的先后顺序
线程串行化
线程运行过程中,依赖另一线程的运行结果(或等待运行一段时间),进入阻塞状态
- final void join()
- final void join(long millis)
- final void join(long millis, int nanos)
线程休眠
- static void sleep(long millis)
- static void sleep(long millis, int nanos)
线程让步
让运行中的线程主动放弃当前CPU处理机会;但不是使线程阻塞,而是转入就绪状态
- static void yield()
线程的挂起和恢复
- final void suspend()
- final void resume()
终止线程
- 不提倡使用Thread下
stop()
方法
线程控制基本方法
方法 | 功能 |
---|---|
isAlive() | |
getPriority() | |
setPriority() | |
sleep() | |
join() | |
yield() | |
suspend()/resume() | |
wait() | 当前线程进入对象的wait pool |
noitfy()/notifyAll() | 唤醒对象的wait pool中的一个/所有的等待线程 |
线程的同步
临界资源问题
原因是对共享数据访问操作的不完整性
互斥锁
- 保证共享数据操作的完整性
每个对象都对应有一个可称为“互斥锁”的标记,保证在任一时刻,只能有一个对象访问该对象
关键字synchronized:表明对象在任一时刻,只能有一个线程访问
两种用法:
方法声明(同步方法):public synchronized void push(char c) { /*...*/ }
修饰语句块(同步块):
public char pop(){ synchronized(this){ /*...*/ } }
线程死锁
举例:
这里写代码片
线程同步通讯
为避免死锁,在线程进入阻塞状态时,应尽量释放其锁定的思源,以为其他的线程提供运行的机会
- final void wait()
- final void notify()
- final void notifyAll()
运行态线程调用对象的wait()方法:① 线程进入阻塞状态(阻塞在对象wait()池中);②解除调用者对象的锁定状态。
其他线程调用同一对象的notify()/notifyAll()方法:获取对象锁后,进入就绪状态。suspend()/resume()不同,其不会释放资源
// 生产者-消费者问题
这里写代码片
多线程编程专题
线程见数据传输
PipeInputStream/PipeOutputStream
- 管道流
- 不提倡在单个线程中使用管道流,而是在多个线程中使用
- 一般也不在高并发环境下使用,避免死锁
类的同步性与线程安全
- Vector(同步安全) vs. ArrayList(非同步安全)
分析:Vector是安全的,但size()和elementAt()方法调用存在状态依赖
修改:
定时器
- Timer
- schedule方法
- TimerTask