运行时数据区:
栈区——跟踪线程运行中一系列的方法调用过程
堆区——对象存储区
方法区——代码
1 线程的创建和启动
1.1 扩展Thread类
主线程调用Thread类的run方法,违背了Thread类提供run()方法的初衷,Thread类的run()方法是专门被自身的线程调用的。
1.2 实现Runnable接口
实现run()方法,较常用。
2 线程的状态转换
- 新建状态New。
- 就绪状态Runnable。调用了该线程的start(),虚拟机为其创建方法调用栈和程序计数器,位于可运行池中。
- 运行状态Running。
- 阻塞状态Blocked。分三种:1)执行了wait(),位于对象等待池中的阻塞状态;2)位于对象锁池中的阻塞状态,试图获得某对象的同步锁,但该锁已被其它线程占用;3)其它,如执行sleep(),或调用其它线程的join(),或发出了I/O请求。
- 死亡状态Dead。不管是正常结束还是异常结束,都不会对其它线程造成影响,可用isAlive()判断。
3 线程调度
线程的调度不是跨平台的,不仅仅取决于java虚拟机,还依赖于操作系统。
希望明确让一个线程给另一个线程运行的机会,可采取以下方法:
(1)调整各线程优先级。
可通过setPriority(int)设置,有3个静态常量:
MAX_PRIORITY:取值10,最大
MIN_PRIORITY:取值1,最小
NORM_PRIORITY:取值5
(2)线程睡眠sleep(),进入堵塞状态。
(3)线程让步 yield(),线程放到可运行池中。
sleep()与yield()区别:
- sleep()给其它线程机会时,不考虑其优先级大小;yield()只会给相同或更高优先级的线程一个运行的机会。
- sleep()抛出InterruptedException异常,yield()没有声明抛出异常。
- sleep()比yield()有更好的可移植性,yield()不能依靠yield()来提高并发性能,常用在测试期间人为提高程序并发性能,帮助发现一些隐藏的错误。
(4)等待其它线程结束。调用另一线程的join()方法,当前线程进入阻塞状态。
4 线程同步
同步方法加synchronized
同步特征:
- 如果一个同步代码块和非同步代码块同时操纵共享资源,仍会造成对共享资源的竞争。
- 每个对象都有唯一的同步锁。
- 静态方法前也可使用synchronized修饰符。
- 当一个线程开始执行同步代码块时,并不意味着必须以不中断的方式运行,也可执行sleep()、yield(),此时并没有放弃锁,只是把运行机会让给了其他线程。
- synchronized声明不会被继承。
线程安全的类:
- 这个类的对象可以同时被多个线程安全地访问。
- 每个线程都能正常执行原子操作,得到正确的结果。
- 每个线程的原子操作都完成后,对象处于逻辑上合理的状态。
不可变类都是线程安全的。
线程类的安全性往往以降低并发性能为代价,为减小这一代价,可采用:
- 只对可能导致资源竞争的代码进行同步。
- 如果一个可变类有两种运行环境——单线程和多线程环境,可以为这个类提供2种实现,单线程运行环境使用未采取同步的类的实现,多线程运行环境使用采取同步的类实现。
以下情况下,持有锁的线程释放锁:
- 执行完同步代码块。
- 执行完同步代码块中,遇到异常终止。
- 执行完同步代码块中,执行了锁所属对象的wait()方法,这个线程释放锁,进入对象的等待池。
以下情况不释放锁:执行完同步代码块中;执行了sleep()或yield(),当前线程放弃CPU,但不会释放锁。
5 线程通信
wait():执行该方法的线程释放对象的锁,虚拟机把该线程放到该对象的等待池中,等待其他线程的唤醒。
notify():执行该方法的线程唤醒在对象的等待池中等待的一个线程。