Java线程调度
线程调度是指系统为线程分配处理器使用权的过程,方式主要有:协同式线程调度和抢占式线程调度。
协同式线程调度
线程的执行时间由线程本身来控制,线程把自己的工作执行完成之后,要主动通知系统切换到另外一个线程上。
- 好处:实现简单。且由于线程要把自己的事情干完后才会进行线程切换,切换操作对线程自己是可知。不存在线程同步问题。
- 缺点:线程执行时间不可控制,甚至如果一个线程编写有问题,一直不告知系统进行线程切换,则程序就会一直阻塞在那里。
抢占式线程调度
每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定(Java中,Thread.yield()可以让出执行时间,但是要获取执行时间的话,线程本身是没有办法的)。
- 好处:线程的执行时间是系统可控的,也不会有一个线程导致整个进程阻塞的问题,Java使用的线程调度方式就是抢占式调度。
线程状态转换
- 新建(New):创建后尚未启动的线程处于这种状态
- 运行(Runable):Runable包括了操作系统线程状态中的Running和Ready,也就是处于此状态的线程可能正在执行,也可能正在等待CPU为它分配执行时间。
- 无限期等待(Waiting):处于这中状态的线程不会被分配CPU执行时间,它们要等待被其他线程显示地唤醒。
- 没有设置Timeout参数的Object.wait()方法。
- 没有设置Timeout参数的Thread.join()方法。
- LockSupport.park()方法。
- 限期等待(Timed Waiting):处于这种状态的线程也不会被分配CPU执行时间,不过无须等待被其他线程显示地唤醒,在一定时间之后由系统自动唤醒。
- Thread.sleep()方法
- 设置Timeout参数的Object.wait()方法
- 设置了Timeout参数的Thread.join()方法
- LockSupport.parkNanos()方法
- LockSupport.parkUntil()方法
- 阻塞(Blocked):线程被阻塞了。在程序等待进入同步区域的时候,线程将进入这种状态。
- “阻塞状态”在等待着获取到一个排他锁,这个时间将在另外一个线程放弃这个锁的时候发生;
- “等待状态”则是在等待一段时间,或者唤醒动作的发生。
- 结束(Terminated):已终止线程的线程状态,线程已经结束执行。
Java中的线程安全
- 不可变
- 不可变(Immutable)的对象一定是线程安全的,无论是对象的方法实现还是方法的调用者,都不需要再采取任何的线程安全保障措施。
- 只要一个不可变的对象被正确地构建出来(没有发生this引用逃逸的情况),则外部的可见状态永远也不会改变,永远也不会看到他在多个线程之中处于不一致的状态。
- 不可变的安全性是最简单、最纯粹的。
- 绝对线程安全
- 相对线程安全
- 线程兼容
- 线程独立
线程安全的实现方法
- 不可变
- 互斥同步
- synchronized实现
- 重入锁(ReentrantLock)实现,相比synchronized,其增加了一些高级功能如等待可中断、可实现公平锁、锁可以绑定多个条件
- 非阻塞同步
- CAS
- AtomicInteger
- 无同步方案
- 可重入代码(Reentrant Code)
- 线程本地存储(Thread Local Storage)
锁优化
- 自旋锁以及自适应自旋
自旋锁的思想是让一个线程在请求一个共享数据的锁时执行忙循环一段时间,如果在这段时间内能获得锁,就可以避免阻塞状态。
自旋虽然避免了线程切换的开销,但它是要占用处理器时间,因此如果锁被占用的时间很短,自旋等待的效果就会非常好,反之如果所被占用的时间很长,那自旋的线程只会白白消耗处理器资源而不会做任何有用的工作,反而带来性能上的浪费。
自适应意味着自旋的时间不在固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。 - 锁消除
指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测不可能存在共享数据竞争的锁进行消除。
主要的判断依据来源于逃逸分析的数据支持,如果判断在一段代码中,堆上的所有数据都不会逃逸出去从而被其他线程访问到,那就可以把它们当做栈上数据对待,认为它们是线程私有,同步加锁自然就无须进行。 - 锁粗化
大部分情况下,总是推荐将同步块的作用范围限制得尽量小(只在共享数据的实际作用域中才进行同步,这样是为了使得需要同步的操作数量尽可能变小,存在锁竞争,则让等待锁的线程能尽快拿到锁),但是如果一系列的连续操作都对同一个对象反复加锁和解锁,甚至加锁操作是出现在循环体中,那即使没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗。
- 轻量级锁
在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。
- 偏向锁
目的是消除数据在无竞争情况下的同步原语,进一步提高程序的运行性能。偏向锁就是在无竞争的情况下把整个同步都消除掉,连CAS操作都不做。