1:synchronized
synchronized (lockObject) {
// update object state
}
synchronized是不错,但它并不完美。它有一些功能性的限制:
- 它无法中断一个正在等候获得锁的线程;
- 也无法通过投票得到锁,如果不想等下去,也就没法得到锁;
- 同步还要求锁的释放只能在与获得锁所在的堆栈帧相同的堆栈帧中进行,多数情况下,这没问题(而且与异常处理交互得很好),但是,确实存在一些非块结构的锁定更合适的情况。
ReentrantLock
类实现了Lock
,它拥有与synchronized
相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Outputter1 {
private Lock lock = new ReentrantLock();// 锁对象
//private Lock lock = new ReentrantLock(true);//公平策略 -->谁等待时间最长,先让谁获取到锁</span>
public void output(String name) {
lock.lock(); // 得到锁
try {
for(int i = 0; i < name.length(); i++) {
System.out.print(name.charAt(i));
}
} finally {
lock.unlock();// 释放锁
}
}
}
区别:
需要注意的是,用sychronized修饰的方法或者语句块在代码执行完之后锁自动释放,而是用Lock需要我们手动释放锁,所以为了保证锁最终被释放(发生异常情况),要把互斥区放在try内,释放锁放在finally内!!
3:Condition(线程通信)Condition可以替代传统的线程间通信,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()。
注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。
Condition的强大之处在于它可以为多个线程间建立不同的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; //写索引
int takeptr; //读索引
int 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();//解除锁定
}
}
实现注意事项
在等待Condition
时,允许发生“
虚假唤醒
”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为
Condition
应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。
4:ReadWriteLock
package cn.crxy.lock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Cache {
/**
* 缓存的实现,每个线程只能获得他自己的缓存,也应该是单例的
* 本类没有去实现单例,如果需要的话可以自行去实现
*/
private static Map<String, Object> cache = new HashMap<String, Object>();
private static ReadWriteLock rwl = new ReentrantReadWriteLock();
// 允许多个读线程并发,但是不允许写线程并发
public static Object get(String key) {
Lock readLock = rwl.readLock();
Object value;
try {
readLock.lock();
value = cache.get(key);
if (value == null) {
Lock writeLock = rwl.writeLock();
readLock.unlock();
try {
writeLock.lock();
if (value == null) {
value = "xxx"; // 查询数据库
cache.put(key, value);
}
} finally {
writeLock.unlock();
}
readLock.lock();
}
} finally {
readLock.unlock();
}
return value;
}
}