(一)Lock接口
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
(二) 四种获取锁方式
-
首先lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
Lock lock = ...; lock.lock(); try{ //处理任务 }catch(Exception ex){ }finally{ lock.unlock(); //释放锁 }
tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
-
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
Lock lock = ...; if(lock.tryLock()) { try{ //处理任务 }catch(Exception ex){ }finally{ lock.unlock(); //释放锁 } }else { //如果不能获取锁,则直接做其他事情 }
-
lockInterruptibly()方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
public void method() throws InterruptedException { lock.lockInterruptibly(); try { //..... } finally { lock.unlock(); } }
(三) ReentrantLock
ReentrantLock,意思是“可重入锁”。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。
第一种获取锁方式:lock.lock():
请求获取锁,如果没有释放锁,则第二个线程没法获取到锁,这种方式可能有死锁现象。
public static void main(String[] args){
Lock lock = new ReentrantLock();
Buys buys = new Buys();
buys.setTotal(100);
MyThread2 dl1 = new MyThread2(buys, lock);
MyThread2 dl2 = new MyThread2(buys, lock);
MyThread2 dl3 = new MyThread2(buys, lock);
MyThread2 dl4 = new MyThread2(buys, lock);
MyThread2 dl5 = new MyThread2(buys, lock);
MyThread2 dl6 = new MyThread2(buys, lock);
dl1.start();
dl2.start();
dl3.start();
dl4.start();
dl5.start();
dl6.start();
}
public class MyThread2 extends Thread {
private Lock lock;
private Buys buys;
public MyThread2(Buys buys, Lock lock){
this.buys = buys;
this.lock = lock;
}
@Override
public void run() {
lock.lock();
System.out.println(this.getName()+":申请锁");
try {
long time = System.currentTimeMillis();
buys.setTotal(buys.getTotal() - 20);
System.out.println(this.getName()+":剩余:"+buys.getTotal());
}catch (Exception e){
}finally {
System.out.println(this.getName()+":释放锁");
lock.unlock();
}
}
}
运行结果如下:
Thread-0:申请锁
Thread-0:剩余:80
Thread-0:释放锁
Thread-1:申请锁
Thread-1:剩余:60
Thread-1:释放锁
Thread-2:申请锁
Thread-2:剩余:40
Thread-2:释放锁
Thread-3:申请锁
Thread-3:剩余:20
Thread-3:释放锁
Thread-4:申请锁
Thread-4:剩余:0
Thread-4:释放锁
Thread-5:申请锁
Thread-5:剩余:-20
Thread-5:释放锁
第二种获取锁方式:lock.trylock():
请求获取锁,有立即获取到返回值(无等待时间),如果成功获取到锁则返回true,否则返回false。该方式不会造成死锁,因为返回false时,线程直接中断。
@Override
public void run() {
if(lock.tryLock()){
System.out.println(this.getName()+":申请锁");
try {
long time = System.currentTimeMillis();
buys.setTotal(buys.getTotal() - 20);
System.out.println(this.getName()+":剩余:"+buys.getTotal());
}catch (Exception e){
}finally {
System.out.println(this.getName()+":释放锁");
lock.unlock();
}
}else{
System.out.println(this.getName()+":获取锁失败");
}
}
运行结果如下:
Thread-0:申请锁
Thread-0:剩余:80
Thread-0:释放锁
Thread-1:申请锁
Thread-2:获取锁失败
Thread-1:剩余:60
Thread-1:释放锁
Thread-4:申请锁
Thread-4:剩余:40
Thread-3:获取锁失败
Thread-5:获取锁失败
Thread-4:释放锁
从运行结果上分析,Thread-2、Thread-3、Thread-5由于没有抢占到资源被直接中断。
第三种获取锁方式:lock.tryLock(1, TimeUnit.NANOSECONDS)
第一个参数是时间数值,第二个参数是时间粒度
TimeUnit.DAYS //天
TimeUnit.HOURS //小时
TimeUnit.MINUTES //分钟
TimeUnit.SECONDS //秒
TimeUnit.MILLISECONDS //毫秒
TimeUnit.NANOSECONDS //毫微秒
TimeUnit.MICROSECONDS //微秒
意思就是,请求获取锁,如果等待1毫微妙,仍然没有获取到锁,则返回false,否则返回true。该方式不会造成死锁,因为返回false时,线程直接中断。
第四种获取锁方式:lockInterruptibly()
public static void main(String[] args){
Lock lock = new ReentrantLock();
Buys buys = new Buys();
buys.setTotal(100);
MyThread2 dl1 = new MyThread2(buys, lock);
dl1.start();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
dl1.interrupt();
}
public class MyThread2 extends Thread {
private Lock lock;
private Buys buys;
public MyThread2(Buys buys, Lock lock){
this.buys = buys;
this.lock = lock;
}
@Override
public void run() {
try {
for(int i=0;i<10000000;i++){
doOption();
}
} catch (InterruptedException e) {
System.out.println("InterruptedException异常:"+this.getName()+":"+e.getMessage());
}
}
private void doOption() throws InterruptedException {
lock.lockInterruptibly();
try {
buys.setTotal(buys.getTotal() - 20);
}catch (Exception e){
System.out.println("Exception:"+e.getMessage());
}finally {
lock.unlock();
}
}
}
LZ对lockInterruptibly研究了很久,从多线程调试改成单线程调试,发现lockInterruptibly和interrupt()配合使用的,可以用代码主动控制中断的时间。其他三种获取锁的方式,只要线程执行了则不能中断,而lockInterruptibly却可以做到立即中断效果。(注:纯属个人理解,有错误请大佬们指出)
(四)ReentrantReadWriteLock
读写锁中的读锁:
public class MyThread2 extends Thread {
private ReadWriteLock lock;
private Buys buys;
public MyThread2(Buys buys, ReadWriteLock lock){
this.buys = buys;
this.lock = lock;
}
@Override
public void run() {
for(int i=0;i<10;i++){
doOption();
}
}
private void doOption() {
lock.readLock().lock();
try {
buys.setTotal(buys.getTotal() + 20);
System.out.println("读操作:"+this.getName()+"---"+buys.getTotal());
}catch (Exception e){
System.out.println("Exception:"+e.getMessage());
}finally {
lock.readLock().unlock();
}
}
}
public static void main(String[] args){
ReadWriteLock lock = new ReentrantReadWriteLock();
Buys buys = new Buys();
buys.setTotal(0);
MyThread2 dl1 = new MyThread2(buys, lock);
dl1.start();
MyThread2 dl2 = new MyThread2(buys, lock);
dl2.start();
}
运行结果如下:
读操作:Thread-0---20
读操作:Thread-1---40
读操作:Thread-0---60
读操作:Thread-1---80
读操作:Thread-0---100
读操作:Thread-1---120
读操作:Thread-0---140
读操作:Thread-1---160
读操作:Thread-0---180
读操作:Thread-0---220
读操作:Thread-0---240
读操作:Thread-0---260
读操作:Thread-0---280
读操作:Thread-0---300
读操作:Thread-1---200
读操作:Thread-1---320
读操作:Thread-1---340
读操作:Thread-1---360
读操作:Thread-1---380
读操作:Thread-1---400
从运行结果来看,多线程用读锁可以加快运行效率。我们再来看看写锁。
读写锁中的写锁:(修改关键代码)
private void doOption() {
lock.writeLock().lock();
try {
buys.setTotal(buys.getTotal() + 20);
System.out.println("写操作:"+this.getName()+"---"+buys.getTotal());
}catch (Exception e){
System.out.println("Exception:"+e.getMessage());
}finally {
lock.writeLock().unlock();
}
}
运行结果如下:
写操作:Thread-0---20
写操作:Thread-0---40
写操作:Thread-0---60
写操作:Thread-0---80
写操作:Thread-0---100
写操作:Thread-0---120
写操作:Thread-0---140
写操作:Thread-0---160
写操作:Thread-0---180
写操作:Thread-0---200
写操作:Thread-1---220
写操作:Thread-1---240
写操作:Thread-1---260
写操作:Thread-1---280
写操作:Thread-1---300
写操作:Thread-1---320
写操作:Thread-1---340
写操作:Thread-1---360
写操作:Thread-1---380
写操作:Thread-1---400
从运行结果来看,如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁,则申请的线程会一直等待释放写锁。
最后呢,LZ想调试一下读写混用的代码的,但是想不到合适的场景,请求大神帮我想想场景,谢谢了。
如果发现有什么地方描述的不对,还请帮忙指出来谢谢。