笔记摘要
这里介绍了java5中的线程锁技术:Lock和Condition,实现线程间的通信,其中的读锁和写锁的使用通过一个缓存系统进行了演示,对于Condition的应用通过一个阻塞队列进行演示。
线程锁技术:Lock & Condition 实现线程同步通信所属包:java.util.concurrent.locks
线程锁 | 说明 |
---|---|
Synchronized | 同步方法,锁对象是this;静态同步方法,锁对象是字节码.class;同步代码块,锁对象是任意对象,但必须是同一个对象 |
Lock | 同步锁接口 |
ReentrantLock | lock(),unlock(),newCondition() |
ReadWriteLock | 读写锁接口 |
ReentrantReadWriteLock | readLock()获取读锁,writeLock()获取写锁 |
Condition | 线程间通信 await()等待 signal()唤醒 |
1. Lock
Lock比传统线程模型中的synchronized方式更加面向对象,相对于synchronized 方法和语句它具有更广泛的锁定操作,此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
于现实生活中类似,锁本身也是一个对象。两个线程执行的代码片段要实现同步互斥的结果,它们必须用同一个Lock对象,锁是上在代表要操作的资源的类的内部方法中,而不是线程代码中。
读写锁
分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由JVM自己控制的。你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!
读写锁的使用情景:
- 如果代码只读数据,就可以很多人共同读取,但不能同时写。
- 如果代码修改数据,只能有一个人在写,且不能同时读数据。
API中ReentrantReadWriteLock类提供的一个读写锁缓存示例:
|
|
读写锁的应用:编写一个缓存系统
注解:为了避免线程的安全问题,synchronized和ReadWriteLock都可以,synchronized也防止了并发读取,性能较低有一个线程先进去,开始读取数据,进行判断,发现没有数据,其他线程就没有必要进去了,就释放读锁,加上写锁,去查找数据写入,为了避免写入的其他对象等待,再做一次判断,数据写入完成后,释放写锁,上读锁,防止写入,还原原来的状态。
两次判断:第一次为了写入数据,所以释放读锁,上写锁。第二次为了防止阻塞的线程重复写入
|
|
虚假唤醒:用while代替if
|
|
读写锁测试
|
|
2. Condition
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法wait和notify的使用
一个锁内部可以有多个Condition,即有多路等待通知,传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。使用一个监视器往往会产生顾此失彼的情况。
在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。
Condition的应用:阻塞队列(使用了两个监视器)
说明:该应用是 java.util.concurrent.locks包中Condition接口中的示例代码。使用了两个Condition分别用于管理取数据的线程,和存数据的线程,这样就可以明确的唤醒需要的一类线程,如果使用一个Condition,当队列满了之后,唤醒的并不一定就是取数据的线程
|
|
Condition测试
输出结果
|
|
3. Condition练习
一共有3个线程,两个子线程先后循环2次,接着主线程循环3次,接着又回到两 个子线程先后循环2次,再回到主线程又循环3次,如此循环5次。
思路:老二先执行,执行完唤醒老三,老三执行完唤醒老大,老大执行完唤醒老二,以此循环,所以定义3个Condition对象和一个执行标识即可
示例出现的问题:两个文件中有同名类的情况
解决方案:可以将一个文件中的那个同名外部类放进类中,但是静态不能创建内部类的实例对象,所以需要加上static,这样两个类的名称就不一样了。 一个是原来的类名,一个是在自己类名前面加上外部类的类名。
|
|
输出结果
多路等待和通知
|
|