Java在java SE 5.0引入了不同与synchronized的另一种加锁方式,java.util.concurrent.Locks包中的两个锁类:ReetrantLock类,为可重入锁。
还有读写锁ReentrantReadWriteLock.
首先介绍synchronized关键字和内部锁。
首先理解每个对象都有一个内部锁,如果一个方法用synchronized声明,那么该对象的锁将保护整个方法,也就是说线程要调用该方法,必须获得内部的对象锁。与Lock和Condition相比,内部对象锁只有一个相关条件和一个等待集,Object的wait方法添加一个线程到等待集,notify/notifyAll解除等待线程的阻塞状态。
由锁来管理进入synchronized方法的线程,由条件来管理那些调用了wait的线程。
将static方法声明为synchronized也是合法的,该方法会调用相关的类对象(该类的class对象)的内部锁。
注意:wait,notify,notifyAll是Object类的final方法。Condition方法必须被命名为await、signal、signalAll以便不发生冲突。
java.util.concurrent.locks
接口Lock
Lock
实现提供了比使用 synchronized
方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的Condition
对象(一个锁可以拥有一个或多个相关的条件对象,用newCondition方法获得一个条件对象)。
接口 Condition
Condition
将 Object
监视器方法(wait
、notify
和notifyAll
)分解成截然不同的对象,以便通过将这些对象与任意Lock
实现组合使用,每个条件有自己的等待 set(wait-set)。其中,Lock
替代了 synchronized
方法和语句的使用,Condition
替代了 Object 监视器方法的使用(await、signal
和 signalAll)。
Condition
实例实质上被绑定到一个锁上。要为特定
Lock
实例获得
Condition
实例,请使用其
newCondition()
方法。
作为一个示例,假定有一个绑定的缓冲区,它支持 put
和 take
方法。如果试图在空的缓冲区上执行 take
操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put
操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存put
线程和take
线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个Condition
实例来做到这一点。
class BoundedBuffer { final Lock lock = new ReentrantLock(); final Condition notFull = lock.newCondition(); final Condition notEmpty = lock.newCondition(); final Object[] items = new Object[100]; int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }
ReetrantLock类,为可重入锁
锁是可重入的,锁保持一个持有计数器(holdcount)来跟踪一个线程对lock方法的嵌套调用,线程应保证每一次调用lock都要调用unlock方法来释放锁。由于可重入特性,被一个锁保护的对象可以使用另一个使用了相同的锁的方法。
此类的构造方法接受一个可选的公平 参数。当设置为 true
时,在多个线程的争用下,这些锁倾向于将访问权授予等待时间最长的线程。否则此锁将无法保证任何特定访问顺序。与采用默认设置(使用不公平锁)相比,使用公平锁的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁和保证锁分配的均衡性时差异较小。不过要注意的是,公平锁不能保证线程调度的公平性。
ReentrantReadWriteLock读写锁
很多线程从一个数据结构读取数据而很少修改其中的数据,那么允许对读者线程共享访问是合适的,但是写者线程依然必须是互斥访问的。
下面时使用读/写锁的必要步骤:
1.构造一个读写锁ReentrantReadWriteLock对象
private ReentrantReadWriteLock rwl=new ReentrantReadWriteLock();
2.抽取读锁和写锁:读写锁为ReentrantReadWriteLock的内部类
private Lock readLock=rwl.readLock();
private Lock writeLock=rwl.writeLock();
readLock():得到一个可以被多个读操作共用的读锁,排斥所有写操作。
writeLock()得到一个写锁,排斥所有其他读操作和写操作。
3.对所有的获取方法加读锁或写锁:
public double read()
{
readLock.lock();
try{ do something ...}
finally{readLock.unlock();}
}
public double write()
{
writeLock.lock();
try{ do something ...}
finally{writeLock.unlock();}
}
把解锁操作放在finally子句之内是至关重要的,如果在临界区的代码抛出异常,锁必须被释放。否则,其他线程将永远阻塞。
读写锁应用的场合
我们有时会遇到对同一个内存区域如数组或者链表进行多线程读写的情况,一般来说有以下几种处理方式: 1.不加任何限制,多见于读取写入都很快的情况,但有时也会出现问题. 2.对读写函数都加以同步互斥,这下问题是没了,但效率也下去了,比如说两个读取线程不是非要排队进入不可. 3.使用读写锁,安全和效率都得到了解决,特别合适读线程多于写线程的情况.也就是下面将要展现的模式.
读写锁的意图
读写锁的本意是分别对读写状态进行互斥区分,有互斥时才加锁,否则放行.互斥的情况有: 1.读写互斥. 2.写写互斥. 不互斥的情况是:读读,这种情况不该加以限制. 程序就是要让锁对象知道当前读写状态,再根据情况对读写的线程进行锁定和解锁。
小结
当多个线程试图对同一内容进行读写操作时适合使用读写锁。
请理解并记住ReadWriteLock类读写锁的写法.
读写锁相对于线程互斥的优势在于高效,它不会对两个读线程进行盲目的互斥处理,当读线程数量多于写线程尤其如此,当全是写线程时两者等效。
Stop,suspend,resume被弃用的原因
stop终止所有未结束的方法,包括run方法。当线程被终止时,立即释放被他锁住的所有对象锁。这会导致对象处于不一致状态。
在希望停止线程的时候应该中断线程,被中断的线程会在安全的时候停止。
suspend挂起线程不释放持有的锁,容易造成死锁。
关于线程中断
http://blog.csdn.net/sunxing007/article/details/9123363
一个线程在未正常结束之前, 被强制终止是很危险的事情. 因为它可能带来完全预料不到的严重后果. 所以你看到Thread.suspend, Thread.stop等方法都被Deprecated了.
那么不能直接把一个线程搞挂掉, 但有时候又有必要让一个线程死掉, 或者让它结束某种等待的状态 该怎么办呢? 优雅的方法就是, 给那个线程一个中断信号, 让它自己决定该怎么办. 比如说, 在某个子线程中为了等待一些特定条件的到来, 你调用了Thread.sleep(10000), 预期线程睡10秒之后自己醒来, 但是如果这个特定条件提前到来的话, 你怎么通知一个在睡觉的线程呢? 又比如说, 主线程通过调用子线程的join方法阻塞自己以等待子线程结束, 但是子线程运行过程中发现自己没办法在短时间内结束, 于是它需要想办法告诉主线程别等我了. 这些情况下, 就需要中断.
中断是通过调用Thread.interrupt()方法来做的. 这个方法通过修改了被调用线程的中断状态来告知那个线程, 说它被中断了. 对于非阻塞中的线程, 只是改变了中断状态, 即Thread.isInterrupted()将返回true; 对于可取消的阻塞状态中的线程, 比如等待在这些函数上的线程, Thread.sleep(), Object.wait(), Thread.join(), 这个线程收到中断信号后, 会抛出InterruptedException, 同时会把中断状态置回为false.
下面的程序会演示对非阻塞中的线程中断:
中断状态可以通过 Thread.isInterrupted()来读取,并且可以通过一个名为 Thread.interrupted()的静态方法读取和清除状态(即调用该方法结束之后, 中断状态会变成false)。
由于 处于阻塞状态的线程 被中断后抛出exception并置回中断状态, 有时候是不利的, 因为这个中断状态可能会作为别的线程的判断条件, 所以稳妥的办法是在处理exception的地方把状态复位:
当代码调用中须要抛出一个InterruptedException, 你可以选择把中断状态复位, 也可以选择向外抛出InterruptedException, 由外层的调用者来决定.
不是所有的阻塞方法收到中断后都可以取消阻塞状态, 输入和输出流类会阻塞等待 I/O 完成,但是它们不抛出 InterruptedException,而且在被中断的情况下也不会退出阻塞状态.
尝试获取一个内部锁的操作(进入一个 synchronized 块)是不能被中断的,但是 ReentrantLock 支持可中断的获取模式即 tryLock(long time, TimeUnit unit)。