JDK并发包(JUC)
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
重入锁 (java.util.concurrent.locks.ReentrantLock)
package Test1;
import java.util.concurrent.locks.ReentrantLock;
public class Demo implements Runnable {
// 重入锁
public static ReentrantLock lock = new ReentrantLock();
static int j = 0;
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
// 获取锁
lock.lock();
try {
j++;
} finally {
// 释放锁
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Demo tl = new Demo();
Thread t1 = new Thread(tl);
Thread t2 = new Thread(tl);
t1.start();
t1.join();
t2.start();
t2.join();
System.out.println(j);
}
}
观察上述代码可以发现,和synchronized相比,重入锁有着显式的操作过程,必须指定何时加锁,何时释放锁所以重入锁对逻辑的控制要远远好于synchronized。但是在退出临界区时必须释放锁。
重入锁允许一个线程连续两次获得同一把锁。如果不允许的话那么同一个线程在第二次获得锁时将会和自己产生死锁。注意的是加锁的次数和释放锁的次数必须相同,如果释放锁的次数过多将会获得一个IllegalMonitorStateException。
// 获取锁
lock.lock();
lock.lock();
try {
j++;
} finally {
// 释放锁
lock.unlock();
lock.unlock();
}
中断响应
对于synchornized来说,如果一个线程在等待锁。那么它要么获取锁继续执行要么保持继续等待。而重入锁提供了另外一种可能,就是线程可以中断。比如你和同学约好去打球,你等了半小时,突然接到一个电话他不能来了,那么你就只能回去了。假如一个线程正在等待锁那么他依然可以收到一个通知,被告知无需等待,可以停止工作了。这对于处理死锁有一定的帮助。
package Test1;
import java.util.concurrent.locks.ReentrantLock;
//中断响应
public class Demo7 implements Runnable {
private int lock;
// 创建重入锁
private static ReentrantLock lock1 = new ReentrantLock();
private static ReentrantLock lock2 = new ReentrantLock();
public Demo7(int lock) {
this.lock = lock;
}
@Override
public void run() {
try {
if (lock == 1) {
// 如果当前线程未中断则获取锁
lock1.lockInterruptibly();
Thread.sleep(1000);
lock2.lockInterruptibly();
} else {
lock2.lockInterruptibly();
Thread.sleep(1000);
lock1.lockInterruptibly();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 查询当前线程是否持有锁
if (lock1.isHeldByCurrentThread())
lock1.unlock();
if (lock2.isHeldByCurrentThread())
lock2.unlock();
System.out.println(Thread.currentThread().getId() + ":线程退出");
}
}
public static void main(String[] args) throws InterruptedException {
Demo7 r1 = new Demo7(1);
Demo7 r2 = new Demo7(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
Thread.sleep(2000);
// 中断一个线程
t2.interrupt();
}
}
在这里我们构建了一个死锁的程序,当主线程在睡眠的时候,线程t1先占用lock1,再占用lock2.而线程t2先占用lock2,再去请求lock1,导致死锁。最后我们中断了线程t2,t2放弃对lock1的请求,同时释放lock2,t1请求lock2成功。执行完毕。
锁申请等待时限
除了等待外部通知以外,要避免死锁还有一种方法就是限时等待。通常一个线程无法拿到锁我们无法判断是死锁了还是产生了饥饿。但是如果我们给定一个等待的时间让线程来自动放弃是有意义的。使用tryLock()进行一次限时等待。
public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
//第一个参数是表示等待时常,第二个参数表示计时单位
public class TimeLock implements Runnable {
public static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
TimeLock tl = new TimeLock();
Thread t1 = new Thread(tl);
Thread t2 = new Thread(tl);
t1.start();
t2.start();
}
@Override
public void run() {
//接收两个参数一个表示等待时常一个表示计时单位。
//在这里由于占用锁的线程会持有锁的时常长达6秒 而另一个线程无法在五秒的等带时间获取锁 请求锁失败
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
Thread.sleep(6000);
} else {
System.out.println("get lock failed");
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
if(lock.isHeldByCurrentThread())
lock.unlock();
}
}
}
无参的示例
package Test1;
import java.util.concurrent.locks.ReentrantLock;
public class TryLock implements Runnable {
public static ReentrantLock lock1 = new ReentrantLock();
public static ReentrantLock lock2 = new ReentrantLock();
int lock;
public TryLock(int lock) {
this.lock = lock;
}
@Override
public void run() {
if (lock == 1) {
while (true) {
if (lock1.tryLock()) {
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
if (lock2.tryLock()) {
try {
System.out.println(Thread.currentThread().getId() + ":退出");
return;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
}
} else {
while (true) {
if (lock2.tryLock()) {
try {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
if (lock1.tryLock()) {
try {
System.out.println(Thread.currentThread().getId() + ":退出");
return;
} finally {
lock1.unlock();
}
}
} finally {
lock2.unlock();
}
}
}
}
}
public static void main(String[] args) {
TryLock r1 = new TryLock(1);
TryLock r2 = new TryLock(2);
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
}
}
无参的情况下,当前线程会尝试会尝试获得锁,如果锁未被其他线程占用,则锁会申请成功,返回true.如果锁被其他线程占用则当前线程不会等待立即返回false.这种模式不会引起线程等待,所以不会产生死锁。
公平锁
公平锁的最大特点是不会产生饥饿现象,只要排队最终是可以得到资源的。如果使用synchronized关键字,那么产生的锁就是非公平的锁。重入锁允许我们对其进行公平性设置。
public ReentrantLock(boolean fair) 当参数设置为true时,表示锁是公平的。但是要实现一个公平锁系统必须维护一个有序队列,所以成本比较高,性能也相对比较底下。如果没有特别的需求,不需要使用公平锁。公平锁和非公平锁在线程调度上的表现也是非常不一样的。
package Test1;
import java.util.concurrent.locks.ReentrantLock;
public class FairLock implements Runnable {
// 公平锁
public ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
FairLock r = new FairLock();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
@Override
public void run() {
while (true) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + ":获得锁");
} finally {
lock.unlock();
}
}
}
}
可以看出如果使用公平锁线程基本上交替获得锁的,几乎不会发生同一个线程连续多次获得锁的情况。从而公平性获得保证。如果使用非公平锁,情况会大不相同。
重入锁重要的api
1.lock() //获得锁 如果锁被占用则等待
2.lockInterruptibly() //获得锁,但是优先响应中断
3.boolean tryLock() //仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。
4.tryLock(long time, TimeUnit unit) //在给定的时间内尝试获得锁
5.unlock() //释放锁
6. isHeldByCurrentThread() //查询当前线程是否保持此锁
重入锁的实现
1.原子态
2.等待队列,所有没有请求到锁的线程,会进入到等待队列,等到有线程释放锁之后,系统从等待队列中唤醒一个线程。
3.阻塞原语park()和unpark() 挂起和恢复
该随笔主要阅读《Java高并发程序设计》总结