秋招准备-Java-并发编程-显式锁Lock(三)
1.Lock接口与其方法,ReentrantLock为实现类
2.Condition接口与其方法,组合ReentrantLock实现条件队列
3.ReentrantReadWriterLock读写锁
1.Lock接口,ReentrantLock实现类
//java.util.concurrent.locks.Lock接口
public interface Lock
{
/**
* Lock实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作
*/
void lock();
void lockInterruptibly();
boolean tryLock();
boolean tryLock(long time, TimeUnit unit);
void unlock();
Condition newCondition();
}
1.lock()普通加锁
Lock lock = new ReentrantLock();
lock.lock();
try {
……
}finally {
lock.unlock();
}
作用相当与synchronized同步块,用lock()方法进行获取锁,如果进入阻塞状态,也会无限期等待。需要手动解锁,要在try块中进行获取锁后的操作,然后在finally块中unlock()确保解锁。
2.trylock()尝试获取锁
if(lock.trylock())
{
try {
……;
}finally {
lock.unlock();
}
}
else {
……
}
trylock()提供尝试获取锁的功能,即trylock()后,如果没有线程持锁,则当前线程获取锁,且返回true,即进入try块,进行获取锁的操作,而如果有线程持锁,则不阻塞阻塞等待,且返回false,因此可以直接进行else块做其他事情。
trylock(long timeout,TimeUnit unit),提供了一段时间的申请,timeout是时间,unit是单位,即在这一段时间内保持请求锁的状态,时间到了还未申请到锁,就返回false放弃了。
3.lockInterruptibly()可中断获取锁
public class Main
{
public static void main(String[] args)throws Exception
{
Lock lock = new ReentrantLock();
Thread t1 = new Thread(new Runnable() {
public void run() {
lock.lock();
try {
Thread.sleep(10000);
}catch(Exception e) {}
finally {
lock.unlock();
}
}
});
t1.start();
Thread.sleep(1000);//让t1拿锁
Thread t2 = new Thread(new Runnable(){
public void run() {
try
{
System.out.println("尝试中");
// lock.lock();
lock.lockInterruptibly();
try
{
System.out.println("一直申请,终于得锁");
}
finally
{
lock.unlock();
}
}
catch(Exception ex)
{
System.out.println("阻塞跳出");
}
}
});
t2.start();
Thread.sleep(1000);
t2.interrupt();
}
}
在synchronized和Lock的普通加锁中,如果一旦进入到等待阻塞,则无法跳出,会在没有持锁前一直等待,而Lock提供了可中断的获取锁功能,如果用lockInterruptibly()来获取锁,当前线程可在阻塞期间通过interrupt()方法来抛出异常并跳出。
在上述代码中,用lockInterruptibly(),t2会在1秒后跳出,用lock()则会10秒后得锁。
4.公平锁与非公平锁
涉及这个“公平”概念的有两点,一是多线程阻塞竞争锁时的顺序,二是等待唤醒队列中单个唤醒的顺序。
而在synchronized中这两点都是随机的。
在Lock机制下,默认是非公平锁,
在竞争锁时,可能发生这样的情况,已经有线程在阻塞等待,而此时,当一个新的线程请求锁时,在这同时,该锁变成可用状态,而阻塞线程又还在启动中,这时这个新线程就会跳过这些阻塞线程获得锁。
显然这是个性能优化,因此非公平锁的性能是要优于公平锁的(基于情况:请求时间短,线程平均持锁时间短),而公平锁则能满足某些情况的功能需求。
ReentrantLock(boolean fair)的构造方法中,提供了一个参数可以申明为公平锁。在公平锁下,竞争锁与唤醒锁都是按先后顺序的。
(底层实现还有点迷,不能把非公平情况的顺序讲得很清楚,未……)
2.Condition条件队列
//java.util.concurrent.locks.Condition接口;
public interface Condition
{
void await();
void signal();
void signalAll();
}
1.Condition的使用是这样的,通过Lock的newCondition()方法,返回一个与Lock实例绑定的Condition对象,然后在Lock的加锁代码段,用这个condition对象,通过条件判断筛选出一些需要暂缓的线程,然后再通过另一些条件判断来唤醒这些线程。
因为可以实例化多个Condition对象,就可以通过不同的条件,筛选出满足不同条件的线程暂缓,因此可以用来实现许多功能,如阻塞队列实际就是通过Condition实现的。
因为唤醒存在虚假唤醒,所以筛选的时候是用while(exp)来筛选的。
2.写一段实验代码,来实现存取钱功能。
class Bank
{
public Bank(int num) {account=num;};
private int account;
public int Account() {return account;}
public void save(int num) { account+=num;}
public void get(int num) { account-=num;}
}
public class Main
{
public static void main(String[] args)throws Exception
{
Bank bank = new Bank(1000);
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
//Get
for(int i=0;i<10;i++)
{
int num = (int)(Math.random()*1000);
Thread t = new Thread(new Runnable() {
public void run() {
lock.lock();
try {
while(bank.Account()<num)
{
condition.await();
}
System.out.print("当前余额:"+bank.Account()+" ");
System.out.print("取出"+num+" ");
bank.get(num);
System.out.println("剩余:"+bank.Account()+" ");
}catch(Exception ex) {}
finally{
lock.unlock();
}
}
});
t.start();
}
for(int i=0;i<10;i++)
{
Thread.sleep(1000);
int num = (int)(Math.random()*1000);
Thread t = new Thread(new Runnable() {
public void run() {
lock.lock();
try {
System.out.print("当前余额:"+bank.Account()+" ");
System.out.print("存入"+num+" ");
bank.save(num);
System.out.println("剩余:"+bank.Account()+" ");
condition.signal();
}catch(Exception ex) {}
finally{
lock.unlock();
}
}
});
t.start();
}
}
}
有时候可能觉得知道了大概的用法,但不知道到底能用在哪,要举例好像也举不出,但实际上自己给需求,自己来做能实验的代码,也是一种培养感觉的方式。
3.ReentrantReadWriteLock
ReentrantReadWriteLock实现的不是Lock接口,而是ReadWriteLock。因此要这样构造对象:
ReentrantReadWriteLock rwl = new ReentrantReadWriteLockd();
ReentrantReadWriteLock的两个内部类ReadLock和WriteLock实现了Lock接口,因此实际的锁这样获得:
Lock read = rwl.readLock();
Lock write = rwl.writeLock();
即readLock()和writeLock()会返回与读写锁对象对应的两把锁,读锁与写锁。
在只读操作中加读锁read,则允许多个读操作线程同时共享数据。
在涉及到写操作的块内加写锁write,一旦有线程进入写锁,则读锁和写锁都开始只允许单线程持锁进入其中一块,即允许读读,不允许读写和写写。
写段实验代码:
class Bank
{
public Bank(int num) {account=num;};
private int account;
public int Account() {return account;}
public void save(int num) { account+=num;}
public void get(int num) { account-=num;}
}
public class Main
{
public static void main(String[] args)throws Exception
{
Bank bank = new Bank(10);
ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
Lock read = rwl.readLock();
Lock write = rwl.writeLock();
int value = 5;
Thread th1 = new Thread(new Runnable() {
public void run() {
read.lock();
try {
Thread.sleep(1000);
System.out.println(bank.Account());
}catch(Exception ex) {}
finally {
read.unlock();
}
}
});
Thread th2 = new Thread(new Runnable() {
public void run() {
read.lock();
try {
bank.save(2);
}catch(Exception ex) {}
finally {
read.unlock();
}
}
});
Thread th3 = new Thread(new Runnable() {
public void run() {
write.lock();
try {
bank.save(3);
}catch(Exception ex) {}
finally {
write.unlock();
}
}
});
th1.start();
// th2.start();
th3.start();
}
}
其实只开读锁的时候就相当于没同步,在里面修改共享数据好像也行,因此也得注意。
4.synchronized与Lock的区别
1.实现层面
jvm与jdk
2.性能层面
synchronized的局限与优化,Lock的稳定
3.功能层面
synchronized简单清晰,但阻塞不可中断,只有一个条件队列。
Lock的功能性强,tryLock(),lockInterruptibly(),提供多个条件队列,公平锁,读写锁。
下一篇: 管程:并发编程的基石