线程优先级
现在主流操作系统(如Windows、Linux、Mac OS X)的任务调度除了具有前面提到的时间片轮转的特点外,还有优先级调度(Priority Schedule)的特点。优先级调度决定了线程按照什么顺序轮流执行,在具有优先级调度的系统中,线程拥有各自的线程优先级(Thread Priority)。具有高优先级的线程会更早地执行,而低优先级的线程通常要等没有更高优先级的可执行线程时才会被执行。
线程的优先级可以由用户手动设置,此外系统也会根据不同情形调整优先级。通常情况下,频繁地进入等待状态(进入等待状态会放弃之前仍可占用的时间份额)的线程(如IO线程),比频繁进行大量计算以至于每次都把所有时间片全部用尽的线程更受操作系统的欢迎。因为频繁进入等待的线程只会占用很少的时间,这样操作系统可以处理更多的任务。我们把频繁等待的线程称之为IO密集型线程(IO Bound Thread),而把很少等待的线程称之为CPU密集型线程(CPU Bound Thread)。IO密集型线程总是比CPU密集型线程更容易得到优先级的提升。
线程饿死:
在优先级调度下,容易出现一种线程饿死的现象。一个线程饿死是说它的优先级较低,在它执行之前总是有比它优先级更高的线程等待执行,因此这个低优先级的线程始终得不到执行。当CPU密集型的线程优先级较高时,其它低优先级的线程就很可能出现饿死的情况;当IO密集型线程优先级较高时,其它线程相对不容易造成饿死的善,因为IO线程有大量的等待时间。为了避免线程饿死,调度系统通常会逐步提升那些等待了很久而得不到执行的线程的优先级。这样,一个线程只要它等待了足够长的时间,其优先级总会被提升到可以让它执行的程度,也就是说这种情况下线程始终会得到执行,只是时间的问题。
在优先级调度环境下,线程优先级的改变有三种方式:
1. 用户指定优先级;
2. 根据进入等待状态的频繁程度提升或降低优先级(由操作系统完成);
3. 长时间得不到执行而被提升优先级。
线程安全与锁
在多个线程并发执行访问同一个数据时,如果不采取相应的措施,将会是非常危险的。假设你在工行有一个银行账户,两张银联卡(自己手里一张,女朋友手里一张),里面有100万。假设取钱就两个过程:1.检查账户余额,2.取出现金(如果要取出的金额 > 账户余额,则取现成功,否则取现失败)。有一天你要买房想把钱取出来,而此时你女朋友也想买一辆车(假设你们事先没有商量)。两个人都在取钱,你在A号ATM机取100万,女朋友在B号ATM机取80万。这时A号ATM检查账户余额发现有100万,可以取出;而与此同时,同一时刻B号ATM也在检查账户余额发现有100万,可以取出;这样,A、B都把钱取出来了。
100万的存款取出180万,银行就亏大发了(当然你就笑呵呵了……)!这就是线程并发的不安全性。为避免这种情况发生,我们要将多个线程对同一数据的访问同步,确保线程安全。
所谓同步(synchronization)就是指一个线程访问数据时,其它线程不得对同一个数据进行访问,即同一时刻只能有一个线程访问该数据,当这一线程访问结束时其它线程才能对这它进行访问。同步最常见的方式就是使用锁(Lock),也称为线程锁。锁是一种非强制机制,每一个线程在访问数据或资源之前,首先试图获取(Acquire)锁,并在访问结束之后释放(Release)锁。在锁被占用时试图获取锁,线程会进入等待状态,直到锁被释放再次变为可用。
二元信号量
二元信号量(Binary Semaphore)是一种最简单的锁,它有两种状态:占用和非占用。它适合只能被唯一一个线程独占访问的资源。当二元信号量处于非占用状态时,第一个试图获取该二元信号量锁的线程会获得该锁,并将二元信号量锁置为占用状态,之后其它试图获取该二元信号量的线程会进入等待状态,直到该锁被释放。
信号量
多元信号量允许多个线程访问同一个资源,多元信号量简称信号量(Semaphore),对于允许多个线程并发访问的资源,这是一个很好的选择。一个初始值为N的信号量允许N个线程并发访问。线程访问资源时首先获取信号量锁,进行如下操作:
1. 将信号量的值减1;
2. 如果信号量的值小于0,则进入等待状态,否则继续执行;
访问资源结束之后,线程释放信号量锁,进行如下操作:
1. 将信号量的值加1;
2. 如果信号量的值小于1(等于0),唤醒一个等待中的线程;
互斥量
互斥量(Mutex)和二元信号量类似,资源仅允许一个线程访问。与二元信号量不同的是,信号量在整个系统中可以被任意线程获取和释放,也就是说,同一个信号量可以由一个线程获取而由另一线程释放。而互斥量则要求哪个线程获取了该互斥量锁就由哪个线程释放,其它线程越俎代庖释放互斥量是无效的。
临界区
临界区(Critical Section)是一种比互斥量更加严格的同步手段。互斥量和信号量在系统的任何进程都是可见的,也就是说一个进程创建了一个互斥量或信号量,另一进程试图获取该锁是合法的。而临界区的作用范围仅限于本进程,其它的进程无法获取该锁。除此之处,临界区与互斥量的性质相同。
读写锁
读写锁(Read-Write Lock)允许多个线程同时对同一个数据进行读操作,而只允许一个线程进行写操作。这是因为读操作不会改变数据的内容,是安全的;而写操作会改变数据的内容,是不安全的。对同一个读写锁,有两种获取方式:共享的(Shared)和独占的(Exclusive)。当锁处于自由状态时,试图以任何一种方式获取锁都能成功,并将锁置为对应的状态;如果锁处于共享状态,其它线程以共享方式获取该锁,仍然能成功,此时该锁分配给了多个线程;如果其它线程试图如独占的方式获取处于共享状态的锁,它必须等待所有线程释放该锁;处于独占状态的锁阻止任何线程获取该锁,不论它们以何种方式。获取读写锁的方式总结如下:
读写锁的状态 |
以共享方式获取 |
以独占方式获取 |
自由 |
成功 |
成功 |
共享 |
成功 |
等待 |
独占 |
等待 |
等待 |
总结-线程安全
当多个线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
当多个线程类并发操作某类的某个方法,(在该方法内部)来修改这个类的某个成员变量的值,不会出错,则我们就说,该的这个方法是线程安全的。
某类的某方法是否线程安全的关键是:
(1) 该方法是否修改该类的成员变量;
(2) 是否给该方法加锁(是否用synchronized关键字修饰)。
多个线程(类)同时执行同一段代码,就可能出现安全问题。
看下面的代码,来理解线程安全
public Double pi() {
int a = 22;
int b = 7;
return new Double(a / b);
}
现在在执行这个方法时,每一个线程都有自己的独立的栈区。当线程进入到方法执行断的时候,一个方法变量在方法代码段中被创建,并保存在线程的栈区(静态方法也放在这里)。不同线程执行这段代码时,会有不同的a/b变量。所以这里是线程安全的,因为没有数据共享。
考虑下面的例子,多线程情况下只执行一次并可以重用结果:
private Double pi = null;
public Double pi() {
if (pi == null) {
pi = new Double(22 / 7);
}
return pi;
}
这个地方虽然优化了,但可惜他不是线程安全的。
两个线程并发执行的时候同时进入到pi==null这个位置,这样可能会new出一个脏的数据.
Consider this example which uses ThreadLocal to make the method pi() again thread-safe while still offering performance gains:
private static ThreadLocal pi = new ThreadLocal();
public Double pi() {
if (pi.get() == null) {
pi.set(new Double(22 / 7));
}
return (Double)pi.get();
}
ThreadLocal类封装了任何类型对象,并把它绑定到当前线程。线程执行pi()方法的时候,实例pi返回的是当前线程的对象。这样的调用是线程安全的。
Writing thread-safe code requires you to be careful when using instance variables or static variables, especially when you are modifying objects that may be used by other threads.
线程不安全
不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
参考文章:http://blog.csdn.net/luoweifu/article/details/46701167