接着上篇java 锁全面解析(一)
四、java.util.corrent包
Lock接口及其实现提供了与synchronized关键字类似的同步功能,与synchronized关键字相比,缺少了隐式释放锁的便捷,但是拥有锁获取和释放的可操作性、可中断的获取锁以及超时获取锁等多种同步特性。
1、Lock具备的特性
1)尝试非阻塞地获取锁
2)能被中断的获取锁
3)超时获取锁
常见的使用:
Lock lock = new ReentrantLock();
lock.lock();
try {
} finally {
lock.unlock();
}
2、Lock底层实现——队列同步器
锁是面向使用者的,它定义了使用者与锁交互的接口,隐藏了实现细节;同步器面向的是锁的实现者,它简化了锁的实现方式,屏蔽了同步状态管理、线程的排队、等待与唤醒等底层操作。
3、Lock基本操作
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
1) 重入锁ReentrantLock
指任意线程在获取锁之后能够再次获取该锁而不被阻塞。释放锁时,线程重复n次获取了锁,需要n次释放其他线程才能获取到锁。
2)公平锁
公平性是针对获取锁而言的。如果一个锁是公平的,那么锁的获取顺序准讯FIFO,但通常情况下公平锁的性能会比非公平锁要低。
3)读写锁
允许多个读线程同时 获取锁,但是在写线程获取锁时,所有的读和其他写线程都将被阻塞。
4)锁降级
锁降级是指写锁降级为读锁。
5)condition接口
Condition定义了等待/通知两种类型的方法,当前线程调用这些方法时,需要提前获取到Condition对象关联的锁。Condition对象由Lock对象创建,所以Condition也是依赖Lock对象的。在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll()。
4、Lock与synchronized的异同
1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
5)Lock可以提高多个线程进行读操作的效率。
五、ThreadLocal
ThreadLocal存放的值是线程内共享的,线程间互斥的,主要用于线程内共享一些数据,避免通过参数来传递。JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。
先了解一下ThreadLocal类提供的几个方法:
1
2
3
4
|
public
T get() { }
public
void
set(T value) { }
public
void
remove() { }
protected
T initialValue() { }
|
简单来看,ThreadLocal类的实现是依托于一个内部类ThreadLocalMap,存取的时候,先获得当前线程,然后获取到当前线程本地变量Map,最后将当前使用的ThreadLocal和传入的值放到Map中,也就是说ThreadLocalMap中存的值是[ThreadLocal对象, 存放的值]。
在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本。初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
这种存储结构的好处:
1、线程死去的时候,线程共享变量ThreadLocalMap则销毁。
2、ThreadLocalMap<ThreadLocal,Object>键值对数量为ThreadLocal的数量,一般来说ThreadLocal数量很少,相比在ThreadLocal中用Map<Thread, Object>键值对存储线程共享变量(Thread数量一般来说比ThreadLocal数量多),性能提高很多。
关于ThreadLocalMap<ThreadLocal, Object>弱引用问题:
当线程没有结束,但是ThreadLocal已经被回收,则可能导致线程中存在ThreadLocalMap<null, Object>的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。
虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,我们有两种手段。
1、使用完线程共享变量后,显示调用ThreadLocalMap.remove方法清除线程共享变量;
2、JDK建议ThreadLocal定义为private static,这样ThreadLocal的弱引用问题则不存在了。
这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。