11.java5的线程锁技术
java.util.concurrent.locks 为锁和等待条件提供一个框架的接口和类,
接口摘要 | ||
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。 | ||
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。 | ||
ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。 | ||
类摘要 | ||
可以由线程以独占方式拥有的同步器。 | ||
以 long 形式维护同步状态的一个 AbstractQueuedSynchronizer 版本。 | ||
为实现依赖于先进先出 (FIFO) 等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。 | ||
用来创建锁和其他同步类的基本线程阻塞原语。 | ||
一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。 | ||
支持与 ReentrantLock 类似语义的 ReadWriteLock 实现。 | ||
Lock比传统线程模型中的synchronized更加面向对象,锁本身也是一个对象,两个线程执行的代码要实现同步互斥效果,就要使用同一个锁对象。锁要上在要操作的资源类的内部方法中,而不是线程代码中。
public interface Lock
所有已知实现类:
ReentrantLock, ReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock
随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁。
方法摘要 | |
void | lock() 获取锁。 |
void | lockInterruptibly() 如果当前线程未被中断,则获取锁。 |
newCondition() 返回绑定到此 Lock 实例的新 Condition 实例。 | |
boolean | tryLock() 仅在调用时锁为空闲状态才获取该锁。 |
boolean | tryLock(long time, TimeUnit unit) 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。 |
void | unlock() 释放锁。 |
Lock与synchronized对比,打印字符串例子
12.java5读写锁技术的妙用
读写锁,分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,由JVM控制。
ReentrantReadWriteLock
构造方法摘要 |
ReentrantReadWriteLock() 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock。 |
ReentrantReadWriteLock(boolean fair) 使用给定的公平策略创建一个新的 ReentrantReadWriteLock。 |
方法摘要 | ||||
protected Thread | getOwner() 返回当前拥有写入锁的线程,如果没有这样的线程,则返回 null。 | |||
protected Collection<Thread> | getQueuedReaderThreads() 返回一个 collection,它包含可能正在等待获取读取锁的线程。 | |||
protected Collection<Thread> | getQueuedThreads() 返回一个 collection,它包含可能正在等待获取读取或写入锁的线程。 | |||
protected Collection<Thread> | getQueuedWriterThreads() 返回一个 collection,它包含可能正在等待获取写入锁的线程。 | |||
int | getQueueLength() 返回等待获取读取或写入锁的线程估计数目。 | |||
int | getReadHoldCount() 查询当前线程在此锁上保持的重入读取锁数量。 | |||
int | getReadLockCount() 查询为此锁保持的读取锁数量。 | |||
protected Collection<Thread> | getWaitingThreads(Condition condition) 返回一个 collection,它包含可能正在等待与写入锁相关的给定条件的那些线程。 | |||
int | getWaitQueueLength(Condition condition) 返回正等待与写入锁相关的给定条件的线程估计数目。 | |||
int | getWriteHoldCount() 查询当前线程在此锁上保持的重入写入锁数量。 | |||
boolean | hasQueuedThread(Thread thread) 查询是否给定线程正在等待获取读取或写入锁。 | |||
boolean | hasQueuedThreads() 查询是否所有的线程正在等待获取读取或写入锁。 | |||
boolean | hasWaiters(Condition condition) 查询是否有些线程正在等待与写入锁有关的给定条件。 | |||
boolean | isFair() 如果此锁将公平性设置为 ture,则返回 true。 | |||
boolean | isWriteLocked() 查询是否某个线程保持了写入锁。 | |||
boolean | isWriteLockedByCurrentThread() 查询当前线程是否保持了写入锁。 | |||
readLock() 返回用于读取操作的锁。 | ||||
toString() 返回标识此锁及其锁状态的字符串。 | ||||
writeLock() 返回用于写入操作的锁。 | ||||
三个线程读数据,三个线程写数据示例:
可以同时读,读的时候不能写,不能同时写,写的时候不能读
读的时候上读锁,读完解锁;写的时候上写锁,写完解锁。注意finally解锁
package cn.itheima;
import java.util.Random;
importjava.util.concurrent.locks.ReadWriteLock;
importjava.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo
{
/**读写所使用
* 三个线程读,三个线程写
*/
publicstatic void main(String[] args)
{
//共享对象
finalSource source = new Source();
//创建线程
for(int i=0; i<3; i++)
{
//读
newThread(new Runnable()
{
publicvoid run()
{
while(true)
source.get();
}
}).start();
//写
newThread(new Runnable()
{
publicvoid run()
{
while(true)
source.put(newRandom().nextInt(999));
}
}).start();
}
}
staticclass Source
{
//共享数据
privateint data = 0;
//要操作同一把锁上的读或写锁
ReadWriteLock rwl =new ReentrantReadWriteLock();
//读方法
publicvoid get()
{
//上读锁
rwl.readLock().lock();
try
{
//获取数据并输出
System.out.println("读——"+Thread.currentThread().getName()+"正在获取数据。。。");
try
{
Thread.sleep(newRandom().nextInt(6)*1000);
}catch (InterruptedException e)
{
e.printStackTrace();
}
System.out.println("读——"+Thread.currentThread().getName()+"获取到的数据:"+data);
}finally
{
//解锁
rwl.readLock().unlock();
}
}
//写方法
publicvoid put(int data)
{
//上写锁
rwl.writeLock().lock();
try
{
//提示信息
System.out.println("写——"+Thread.currentThread().getName()+"正在改写数据。。。");
try
{
Thread.sleep(newRandom().nextInt(6)*1000);
}catch (InterruptedException e)
{
e.printStackTrace();
}
this.data= data;
System.out.println("写——"+Thread.currentThread().getName()+"已将数据改写为:"+data);
}finally
{
//解锁
rwl.writeLock().unlock();
}
}
}
}
JDK帮助文档中的示例用法。下面的代码展示了如何利用重入来执行升级缓存后的锁降级(为简单起见,省略了异常处理):
class CachedData {
Object data;
volatile boolean cacheValid; 数据有没有标记
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {处理数据
rwl.readLock().lock();先上读锁
if (!cacheValid) {如果数据不存在
// Must release read lock before acquiring write lock
rwl.readLock().unlock();准备写数据,需先解除读锁
rwl.writeLock().lock();上写锁
// Recheck state because another thread might have acquired
// write lock and changed state before we did.
if (!cacheValid) {再次检查数据是否存在,防止其他线程已经存入数据
data = ...
cacheValid = true;写好数据,改变标记
}
// Downgrade by acquiring read lock before releasing write lock
准备释放写锁,数据存在了,释放后就要使用数据,恢复产生数据前的读锁状态
rwl.readLock().lock();
rwl.writeLock().unlock(); // Unlock write, still hold read
}
use(data);存在直接使用数据
rwl.readLock().unlock();解除读锁
}
}
面试题:设计一个缓存系统
缓存系统:你要取数据,需调用我的public ObjectgetData(String key)方法,我要检查我内部有没有这个数据,如果有就直接返回,如果没有,就从数据库中查找这个数,查到后将这个数据存入我内部的存储器中,下次再有人来要这个数据,我就直接返回这个数不用再到数据库中找了。 你要取数据不要找数据库,来找我。
class CachedSystem
{ 缓存系统的存储器
privateMap<String, Object> cache = new HashMap<String, Object>();
取数据方法 可能有多个线程来取数据,没有数据的话又会去数据库查询,需要互斥
publicsynchronizedObject get(String key)
{ 先查询内部存储器中有没有要的值
Objectvalue = cache.get(key);
if(value==null)如果没有,就去数据库中查询,并将查到的结果存入内部存储器中
{
value= “aaaa”; 实际代码是查询后的结果 queryDB(key)
cache.put(key,value);
}
return value;
}
}
上面的代码每次只能有一个线程来查询,但只有写的时候才需要互斥,修改如下
来一个读写锁
ReadWriteLockrwl = new ReentrantReadWriteLock();
public Object get(String key)
{
上读锁
rwl.readLock().lock();
先查询内部存储器中有没有要的值
Objectvalue = cache.get(key);
if(value==null)如果没有,就去数据库中查询,并将查到的结果存入内部存储器中
{
释放读锁 上写锁
rwl.readLock().unlock();
rwl.writeLock().lock();
if(value==null)再次进行判断,防止多个写线程堵在这个地方重复写
{
value = “aaaa”;
cache.put(key, value);
}
设置完成 释放写锁,恢复读写状态
rwl.readLock().lock();
rwl.writeLock().unlock();
}
释放读锁
rwl.readLock().unlock();
return value; 注意:try finally中unlock
}
13.java5条件阻塞Condition的应用
Condition的功能类似在传统线程技术中的Object.wait()和Object.natify()的功能,传统线程技术实现的互斥只能一个线程单独干,不能说这个线程干完了通知另一个线程来干,Condition就是解决这个问题的,实现线程间的通信。比如CPU让小弟做事,小弟说我先歇着并通知大哥,大哥就开始做事。
public interface Condition
Condition 将 Object 监视器方法(wait、notify和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
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();
}
}
}
使用方法:
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
this.wait()àcondition.await()
this.notify()àcondition.signal()
注意:判断条件时用while防止虚假唤醒,等待在那里,唤醒后再进行判断,确认符合要求后再执行任务。
14.java5的Semaphore同步工具
Semaphore可以维护当前访问自身的线程个数,并且提供了同步机制。
semaphore实现的功能类似于厕所里有5个坑,有10个人要上厕所,同时就只能有5个人占用,当5个人中 的任何一个让开后,其中在等待的另外5个人中又有一个可以占用了。
java.util.concurrent.Semaphore
一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个acquire(),然后再获取该许可。每个 release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore 只对可用许可的号码进行计数,并采取相应的行动。
Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问:
class Pool {
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}
//Not a particularly efficient data structure; just for demo
protected Object[] items = ... whatever kinds of items being managed
protected boolean[] used = new boolean[MAX_AVAILABLE];
protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null; // not reached
}
protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else
return false;
}
}
return false;
}
}
获得一项前,每个线程必须从信号量获取许可,从而保证可以使用该项。该线程结束后,将项返回到池中并将许可返回到该信号量,从而允许其他线程获取该项。注意,调用acquire()时无法保持同步锁,因为这会阻止将项返回到池中。信号量封装所需的同步,以限制对池的访问,这同维持该池本身一致性所需的同步是分开的。
构造方法摘要 | ||||
Semaphore(int permits) 创建具有给定的许可数和非公平的公平设置的 Semaphore。 | ||||
Semaphore(int permits, boolean fair) 创建具有给定的许可数和给定的公平设置的 Semaphore。 | ||||
方法摘要 | ||||
void | ||||
void | acquire(int permits) 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断。 | |||
void | acquireUninterruptibly() 从此信号量中获取许可,在有可用的许可前将其阻塞。 | |||
void | acquireUninterruptibly(int permits) 从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。 | |||
int | availablePermits() 返回此信号量中当前可用的许可数。 | |||
int | drainPermits() 获取并返回立即可用的所有许可。 | |||
protected Collection<Thread> | getQueuedThreads() 返回一个 collection,包含可能等待获取的线程。 | |||
int | getQueueLength() 返回正在等待获取的线程的估计数目。 | |||
boolean | hasQueuedThreads() 查询是否有线程正在等待获取。 | |||
boolean | isFair() 如果此信号量的公平设置为 true,则返回 true。 | |||
protected void | reducePermits(int reduction) 根据指定的缩减量减小可用许可的数目。 | |||
void | release() 释放一个许可,将其返回给信号量。 | |||
void | release(int permits) 释放给定数目的许可,将其返回到信号量。 | |||
toString() 返回标识此信号量的字符串,以及信号量的状态。 | ||||
boolean | tryAcquire() 仅在调用时此信号量存在一个可用许可,才从信号量获取许可。 | |||
boolean | tryAcquire(int permits) 仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。 | |||
boolean | tryAcquire(int permits, long timeout, TimeUnit unit) 如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。 | |||
boolean | tryAcquire(long timeout, TimeUnit unit) 如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断,则从此信号量获取一个许可。 | |||
示例:3个坑 10个人
厕所,有多少人都能装,线程数动态变化,来一个人产生一个线程
ExecutorService service =Exccutors.newCachedThreadPool();
final Semaphore sp = new Semaphore(3);厕所中坑的个数 指定只有3个
3个坑,来了5个人,有2个人要等,其中有一个办完事走了,等待的2个哪个先上呢?默认的构造方法不管,谁抢到了谁上。用newSemaphore(3, true)就可以保证先来的先上。
将坑的个数设置为1就可以达到互斥效果,每次只能有一个线程运行
for (int i=0; i<10; i++)来了10个人
{人的任务 抢坑
Runnablerunnable = new Runnable()
{
public void run()
{
sp.acquire();抢坑了 会抛中断异常
}有人占住坑了,给出提示
SOP(currentThreadName+进入,当前已有(3-sp.availablePermits())个人了)
Thread.sleep(5000)蹲坑办事
办完事打声招呼
SOP(ThreadName即将离开)
释放坑的占有权
sp.release();
SOP(ThreadName已经走了,还有sp.availablePermits()个坑可用)
}
开始任务吧
service.execute(runnable)
}
传统互斥只能内部释放锁this.unlock(),进去this.lock()晕倒了别人就没法进去了;用信号灯可以外部释放,其他线程可以释放再获取sp.release() sp.acquire()。