OK, 什么是内置锁?
内置锁使用比较简单,再方法上加入synchronized关键字或者在需要调用的地方添加synchronized(Obj){}块即可;
这里主要说一下显示锁:
OK, 什么是显示锁?
很简单,通过一个显示的对象,来手动开启和关闭一个锁。Java util的concurrent包下面有一个locks包,包下有一个lock接口,该接口就是实现显示锁的底层类,ReentrantLock类就是最常用的显示锁。
为什么要显示锁?
优点:显示锁可以使你的代码更加灵活,提供锁可等待、可中断、可重入、公平性策略等机制;在性能方面,jdk5显示锁的性能远远大于内置锁,jdk6中也比内置锁高一些,6中对内置锁做了优化;
缺点:显示锁使用的危险性比内置锁要高,比如如果你忘记在finally块中释放锁,程序可以正常运行,但是,这是一个定时炸&弹,一旦爆发,不可控的结果很可怖!另,因为内置锁是在方法描述中添加的关键字,所以,当线程在执行的时候,在线程转储中能看到哪些调用栈帧中获得了哪些锁,非常有利于调试,而显示锁是一个对象,并没有办法看到 哪些调用栈帧中获得了哪些锁。
显示锁怎么用?
显示锁有以下几种: ReentrantLock(保守锁,又有叫互斥锁)、RentrankReadWriteLock(读写锁)
1、ReentrantLock示例:
class Operations {
// 创建一个显示锁非公平策略显示锁
final ReentrantLock rwLock = new ReentrantLock();
@SuppressWarnings("static-access")
public void exec(long waitTime) {
// 获得锁
rwLock.lock();
try {
System.out.println(String.format(
"Operation - doRead() - I am [%s] read now ", Thread
.currentThread().getName()));
Thread.currentThread().sleep(waitTime);
System.out.println(String.format(
"Operation - doRead() - I am [%s] read OK.%s", Thread
.currentThread().getName(), waitTime));
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放锁
rwLock.unlock();
}
}
}
public static void main(String[] args) {
final Operations op = new Operations();
// 调用线程1,获得锁后,停止1分钟
Thread r1 = new Thread(new Runnable() {
@Override
public void run() {
op.exec(10000L);
}
});
Thread r2 = new Thread(new Runnable() {
@Override
public void run() {
op.exec(20000L);
}
});
r1.start();
r2.start();
}
看Operations类,显示的使用一定是和try{}catch(){}finally{}一起使用,在finally中一定要释放锁,当调用Reentrant.lock方法后,他会判断计数是否为零,为零则认为对象没有被锁定,计数+1,调用unlock方法后,同理计数-1,如果为0,则释放锁,别的线程可获得该锁。注意,该锁可以被重入,同一个线程可以递归的获取锁! 此锁最多支持同一个线程发起的 2147483648 个递归锁。试图超过此限制会导致由锁方法抛出的Error。上面代码输出:
Operation - doRead() - I am [Thread-0] read now
Operation - doRead() - I am [Thread-0] read OK.10000
Operation - doRead() - I am [Thread-1] read now
Operation - doRead() - I am [Thread-1] read OK.20000
OK,上面是最简单的使用,那么,当有一个线程A在持有锁的时候,又有10个线程在等待锁释放状态,这时候A释放了锁,那么,10个线程是先调用哪个?
上面代码是非公平的策略,即,让等待的10个线程抢锁,谁抢到归谁!如何配置公平策略?在显示锁的构造函数里面,有一个boolean参数,默认false(非公平),如果设置为true则为公平策略,即:哪个等待的时间最长,就优先让哪个线程获得锁。
总结:Reentrant实现了一种最标准的互斥锁,每次仅允许一个线程持有锁,但是很多情况下,对于一个公共的内存对象,你可能需要允许多个线程同时去读,仅一个线程去写,这时候就需要用到读-写锁
2、RentrankReadWriteLock
说明:
一个资源可以被多个读操作访问,或者只能被一个写操作访问,并且读、写操作不能同时存在。
示例:
class Operation {
// 定义一个读写锁
final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 定义一个读锁
final Lock r = rwLock.readLock();
// 定义一个写锁
final Lock w = rwLock.writeLock();
@SuppressWarnings("static-access")
public void doRead(long waitTime) {
// 获得读锁
r.lock();
try {
System.out.println(String.format(
"Operation - doRead() - I am [%s] read now ", Thread
.currentThread().getName()));
// 持有读锁的状态下休眠
Thread.currentThread().sleep(waitTime);
System.out.println(String.format(
"Operation - doRead() - I am [%s] read OK.%s", Thread
.currentThread().getName(), waitTime));
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放锁
r.unlock();
}
}
public void doWrite() {
// 获得一个写锁
w.lock();
try {
System.out.println(String.format(
"Operation - doRead() - I am [%s] wirte now ", Thread
.currentThread().getName()));
} finally {
// 释放一个写锁
w.unlock();
}
}
}
public class ReadWriteLockMain {
public static void main(String[] args) {
final Operation op = new Operation();
// 读线程1 休眠10s
Thread r1 = new Thread(new Runnable() {
@Override
public void run() {
op.doRead(10000L);
}
});
// 读线程2 休眠20s
Thread r2 = new Thread(new Runnable() {
@Override
public void run() {
op.doRead(20000L);
}
});
// 读线程3 休眠30s
Thread r3 = new Thread(new Runnable() {
@Override
public void run() {
op.doRead(30000L);
}
});
// 写操作线程
Thread w1 = new Thread(new Runnable() {
@Override
public void run() {
op.doWrite();
}
});
r1.start();
r2.start();
r3.start();
w1.start();
}
}
在操作中,线程r1,r2,r3同时获得读锁,r1休眠10s后释放,r2休眠20s后释放,r3休眠30s释放,w1获取读锁;输出中,r1、r2、r3全部同时打印结果,当全部释放后,w1才能获得锁;
Operation - doRead() - I am [Thread-0] read now
Operation - doRead() - I am [Thread-1] read now
Operation - doRead() - I am [Thread-2] read now
Operation - doRead() - I am [Thread-0] read OK.10000
Operation - doRead() - I am [Thread-1] read OK.20000
Operation - doRead() - I am [Thread-2] read OK.30000
Operation - doRead() - I am [Thread-3] wirte now