ReentrantLock 重入锁
2018-10-26 本文已影响0人
larQ
ReentrantLock是jdk1.5里面的, 可以理解为相当于 Synchronized,但二者实现机制不一样,总之是 加锁,同步代码
下面看一个示例
public class Test {
Lock lock = new ReentrantLock();
void m1() {
try {
lock.lock(); //加锁
for (int i = 0; i < 10; i++) {
System.out.println("m1() - " + i);
TimeUnit.SECONDS.sleep(1);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); //释放锁
}
}
void m2() {
lock.lock(); //加锁
System.out.println("m2()");
lock.unlock(); //释放锁
}
public static void main(String[] args) {
final Test t = new Test();
new Thread(new Runnable() {
@Override
public void run() {
t.m1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
t.m2();
}
}).start();
//运行后可以看到 m1() 先打印10次 在打印没m2()
//lock.lock() 到 lock.unlock() 之间的代码是同步的
}
}
尝试锁
直接上代码 注释在代码中
public class Test {
Lock lock = new ReentrantLock();
void m1() {
lock.lock();
for (int i = 0; i < 10; i++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m1() - "+i);
}
lock.unlock();
}
void m2() {
//尝试锁,尝试去获取锁
boolean isLocked = false;
try {
//尝试锁, 如果有锁,无法获取锁标记,返回false。
//如果获取锁标记,返回true
//isLocked = lock.tryLock();
// 阻塞尝试锁,阻塞参数代表的时长,尝试获取锁标记。
// 如果超时,不等待。直接返回。
//这里表示在5秒 内去尝试获取锁,如果能获取到返回true,否则返回false
isLocked = lock.tryLock(5, TimeUnit.SECONDS);
if(isLocked) {
System.out.println("m2() is synchronized");
}else {
System.out.println("m2() is unSynchronized");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(isLocked) {
// 尝试锁在解除锁标记的时候,一定要判断是否获取到锁标记。
// 如果当前线程没有获取到锁标记,会抛出异常。
//就是说 这里的if判断有必要写一下
lock.unlock();
}
}
}
public static void main(String[] args) {
Test t = new Test();
new Thread(new Runnable() {
@Override
public void run() {
t.m1();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
t.m2();
}
}).start();
}
}
可打断
阻塞状态: 包括普通阻塞,等待队列,锁池队列。
- 普通阻塞: sleep(10000), 可以被打断。调用thread.interrupt()方法,可以打断阻塞状态,抛出InterruptedException异常。
- 等待队列: wait()方法被调用,也是一种阻塞状态,只能由notify唤醒。无法打断
- 锁池队列: 无法获取锁标记。不是所有的锁池队列都可被打断。
①使用ReentrantLock的lock方法,获取锁标记的时候,如果需要阻塞等待锁标记,无法被打断。
②使用ReentrantLock的lockInterruptibly方法,获取锁标记的时候,如果需要阻塞等待,可以被打断,打断后抛出InterruptedException异常。
下面看一段代码
public class Test {
Lock lock = new ReentrantLock();
void m1() {
lock.lock();
for (int i = 0; i < 5; i++) { //循环5次 每一秒 打印一下
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("m1() - "+ i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
void m2() {
try {
//也是加锁 但是 可尝试打断,阻塞等待锁。可以被其他的线程打断阻塞状态
lock.lockInterruptibly();
//lock.lock();
System.out.println("m2()");
} catch (InterruptedException e) {
//被其他的线程打断后 进入到这个异常
System.out.println("m2() interrupted");
}finally {
try{
//如果没有锁的话,执行lock.unlock(); 会抛出异常
lock.unlock();
}catch(Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Test t = new Test();
new Thread(new Runnable() { //
@Override
public void run() {
t.m1();
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//2秒后 启动t2线程
Thread t2 = new Thread(new Runnable() { //t2线程
@Override
public void run() {
t.m2();
}
});
t2.start();
//打断 。 非正常结束阻塞状态的线程,都会抛出异常。
//如果m2() 中第一行 换成lock.lock(); 则不会抛出异常
t2.interrupt();
}
}
公平锁
一般来说 多个线程去获取一把锁是属于竞争关系的,不管等的时间有多久。
比如 t1 先获取到了锁,在运行阶段,t2,t3,t4都在阻塞等待,当t1释放锁后,那么t2,t3,t4回去竞争 那把锁,这个是随机的 ,不管哪个线程等的多久
ReentrantLock 可以实现公平锁,实现等待时间长的线程先执行
Synchronized没有公平性
//定义公平锁 就是 加上 参数 true
Lock lock = new ReentrantLock(true);
这种比如 斗地主的 轮询等待时间
ReentrantLock生产者 消费者 Condition
public class Test<E> {
private final LinkedList<E> list = new LinkedList<>();
private final int MAX = 10;
private int count = 0;
private Lock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition consumer = lock.newCondition();
public int getCount(){
return count;
}
public void put(E e){
lock.lock();
try {
while(list.size() == MAX){
System.out.println(Thread.currentThread().getName() + " 等待。。。");
// 进入等待队列。释放锁标记。
// 借助条件,进入的等待队列。
producer.await();
}
System.out.println(Thread.currentThread().getName() + " put 。。。");
list.add(e);
count++;
// 借助条件,唤醒所有的消费者。
consumer.signalAll();
} catch (InterruptedException e1) {
e1.printStackTrace();
} finally {
lock.unlock();
}
}
public E get(){
E e = null;
lock.lock();
try {
while(list.size() == 0){
System.out.println(Thread.currentThread().getName() + " 等待。。。");
// 借助条件,消费者进入等待队列
consumer.await();
}
System.out.println(Thread.currentThread().getName() + " get 。。。");
e = list.removeFirst();
count--;
// 借助条件,唤醒所有的生产者
producer.signalAll();
} catch (InterruptedException e1) {
e1.printStackTrace();
} finally {
lock.unlock();
}
return e;
}
public static void main(String[] args) {
final Test<String> c = new Test<>();
for(int i = 0; i < 10; i++){
new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 5; j++){
System.out.println(c.get());
}
}
}, "consumer"+i).start();
}
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
for(int i = 0; i < 2; i++){
new Thread(new Runnable() {
@Override
public void run() {
for(int j = 0; j < 25; j++){
c.put("container value " + j);
}
}
}, "producer"+i).start();
}
}
}
最后
1,重入锁ReentrantLock,建议应用的同步方式,相对效率比synchronized高,量级较轻
2,synchronized在jdk1.5开始尝试优化,到1.7版本级之后 效率已经非常好了,在绝对效率上 不比synchronized差多少
3,使重入锁 ,必须手工释放锁标记,一般在finally中释放(unlock()方法)