1.可重入锁Lock
锁主要是为了保护临界区资源,防止由于并发执行而引起的数据异常。而synchronized关键字就是一种最简单的控制方法。经过jdk1.6对synchronized的优化,Lock和synchronized的性能相差并不多。 那么为什么还需要Lock,这当然有它的用处,
先看一个示例,锁的普通情况的使用:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo implements Runnable {
static int i = 0; //声明为静态变量,否则无法直接在main方法中调用
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
lock.lock(); //进行同步控制
try {
++i;
} finally {
lock.unlock(); //加锁部分写进try finally语句中,保证锁一定会被释放掉
}
}
}
public static void main(String[] args) {
ReentrantLockDemo lockDemo = new ReentrantLockDemo();
Thread t1 = new Thread(lockDemo);
Thread t2 = new Thread(lockDemo);
t1.start();
t2.start();
try {
t1.join(); //等待t1线程和t2线程执行完毕
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(i);
}
}
Lock额外提供可几个以下的功能:
1)可重入
之所以把Lock称作可重入锁,是因为这把锁是可以反复进入的,当然这里反复进入仅仅局限于一个线程。上述代码的加锁部分,也可以加两把锁,如下:
lock.lock();
lock.lock();
try {
++i;
} finally {
lock.unlock();
lock.unlock();
}
注意:如果同一个线程多次获得锁,那么也必须释放相同次数的锁;如果释放次数多,那么会得到一个java.lang.IllegalMonitorStateException异常;如果释放次数少,那么其它线程将不能进入临界区。
2)中断响应
对于synchronized来说,如果一个线程在等待锁,那么结果只有两种情况,要么它获得锁继续执行,要么就等待。而使用重入锁,那么锁可以被中断,即在等待过程中,程序可以根据需要取消对锁的请求。
import java.util.concurrent.locks.ReentrantLock;
public class IntLock implements Runnable{
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
int lock;
public IntLock(int lock) {
this.lock = lock;
}
@Override
public void run() {
//这里的if else主要是为了模拟死锁操作,这样可以看到通过调用中断方法,一个线程被中断,而另一个线程正常执行
try {
if (lock == 1){
lock1.lockInterruptibly(); //调用这个方法加锁,同时可以响应中断
Thread.sleep(500);
lock2.lockInterruptibly();
System.out.println(Thread.currentThread().getId() + ": 完成");
}else {
lock2.lockInterruptibly();
Thread.sleep(500);
lock1.lockInterruptibly();
System.out.println(Thread.currentThread().getId() + ": 完成");
}
} 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) {
IntLock intLock1 = new IntLock(1);
IntLock intLock2 = new IntLock(2);
Thread t1 = new Thread(intLock1);
Thread t2 = new Thread(intLock2);
t1.start();
t2.start();
t1.interrupt(); //中断线程1
}
}
3)锁申请等待限时
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TryLockDemo implements Runnable{
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
if(lock.tryLock(5, TimeUnit.SECONDS)){ //锁申请等待限时,等待5秒钟
Thread.sleep(6000);
System.out.println(Thread.currentThread().getId() + "complete!");
}else {
System.out.println(Thread.currentThread().getId() + "get lock failed");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(lock.isHeldByCurrentThread()) //如果当前线程持有该锁,则释放锁
lock.unlock();
}
}
public static void main(String[] args) {
TryLockDemo tryLockDemo = new TryLockDemo();
Thread t1 = new Thread(tryLockDemo);
Thread t2 = new Thread(tryLockDemo);
t1.start();
t2.start();
}
}
tryLock()也可以不带参数直接运行,在这种情况下,如果申请不成功,则直接返回false,不会等待。
4)总结
public void lock():获得锁,如果锁已经被占用,则等待;
public void lockInterruptibly():获得锁,但优先响应中断;
public boolean tryLock():尝试获得锁,如果成功返回true,继续执行;如果失败,返回false,不等待;
boolean tryLock(long timeout, TimeUnit unit):锁申请等待限时;
public void unlock():释放锁;
2.线程通信 (Condition)
Condition提供了一下几个方法:
void await() throws InterruptedException;
void awaitUninterruptibly();
boolean await(long time, TimeUnit unit) throws InterruptedException;
void signal();
void signalAll();
其和Object.wait()、Object.notify()方法作用类似;
await()方法使当前线程等待,同时释放锁;在其它线程中调用signal()方法或者signalAll()方法,线程会被唤醒,获得锁继续执行。当线程被中断时,也能跳出等待;
awaitUninterruptibly()作用和await()方法类似,但它不响应中断;
signal()方法用于唤醒线程。
示例如下:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionDemo implements Runnable {
private static Lock lock = new ReentrantLock(); //把变量声明为静态变量,这样可以直接在main方法中使用
private static Condition condition = lock.newCondition();
@Override
public void run() {
try {
lock.lock();
condition.await();
System.out.println("执行完毕");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ConditionDemo conditionDemo = new ConditionDemo();
Thread t1 = new Thread(conditionDemo);
t1.start();
Thread.sleep(2000);
//通知线程t1继续执行
lock.lock(); //和Object的notify方法同理,这里也需要先获得重入锁,才能执行signal()方法
condition.signal();
lock.unlock(); //在唤醒线程之后,需要释放锁;如果省略这行代码,那么就算t1被唤醒,但由于它无法获得重入锁,
//因而就无法继续执行。
}
}
一旦线程被唤醒,它会重新尝试获得与其绑定的重入锁,如果成功获取就继续执行。