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();@Overridepublic 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;}@Overridepublic 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();@Overridepublic 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();@Overridepublic 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被唤醒,但由于它无法获得重入锁,//因而就无法继续执行。}
}
一旦线程被唤醒,它会重新尝试获得与其绑定的重入锁,如果成功获取就继续执行。