Java并发分析—Lock
1.Lock 和 Condition
当使用synchronied进行同步时,可以在同步代码块中只用常用的wait和notify等方法,在使用显示锁的时候,将通过Condition对象与任意Lock实现组合使用,为每个对象提供多个等待方法,其中Lock代替了synchronized方法和语句的使用,Condition代替了Object监视器方法的使用,条件Condition为线程提供了一个含义,以便在某个状态下出现可能为true,另一个线程通知它之前,一直挂起该线程,即让其等待,因为访问该共享状态信息发生在不同的线程中,所以它必须受到保护。
关于显示锁和内部锁可参考:https://blog.csdn.net/qq_32608881/article/details/107016817
2.Lock 和 ReentrantLock
Lock 接口定义了一组抽象的锁定操作。与内部锁定(intrinsic locking)不同,Lock 提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有加锁和解锁的方法都是显式的。这提供了更加灵活的加锁机制,弥补了内部锁在功能上的一些局限——不能中断那些正在等待获取锁的线程,并且在请求锁失败的情况下,必须无限等待。
Lock 接口主要定义了下面的一些方法,并通过ReentrantLock实现Lock 接口:
1 package com.test; 2 3 import java.util.concurrent.TimeUnit; 4 import java.util.concurrent.locks.Condition; 5 import java.util.concurrent.locks.Lock; 6 7 public class LockTest implements Lock{ 8 9 /** 10 * lock()用来获取锁。如果锁已被其他线程获取,则进行等待 11 */ 12 public void lock() {} 13 14 /** 15 * 通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待, 16 * 那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。 17 */ 18 public void lockInterruptibly() throws InterruptedException {} 19 20 /** 21 * 表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false, 22 * 这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待 23 */ 24 public boolean tryLock() { 25 return false; 26 } 27 28 /** 29 * 这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。 30 * 如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true 31 */ 32 public boolean tryLock(long time, TimeUnit unit) 33 throws InterruptedException { 34 return false; 35 } 36 37 /** 38 * 释放锁,必须在finally中释放 39 */ 40 public void unlock() {} 41 42 public Condition newCondition() { 43 return null; 44 } 45 }
(1)void lock():获取锁。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态。
例:
1 package com.test; 2 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 public class Main { 7 Lock lock = new ReentrantLock(); 8 private int i = 0; 9 10 public static void main(String[] args) { 11 final Main main = new Main(); 12 new Thread(new Runnable() { 13 public void run() { 14 main.write(Thread.currentThread()); 15 } 16 }).start(); 17 new Thread(new Runnable() { 18 public void run() { 19 main.write(Thread.currentThread()); 20 } 21 }).start(); 22 } 23 24 public void write(Thread thread) { 25 lock.lock(); 26 try { 27 System.out.println(thread.getName() + "获取了锁"); 28 i = 1; 29 } catch (Exception e) { 30 } finally { 31 lock.unlock(); 32 System.out.println(thread.getName() + "释放了锁"); 33 } 34 } 35 }
运行结果:
Thread-0获取了锁 Thread-0释放了锁 Thread-1获取了锁 Thread-1释放了锁
(2)void lockInterruptibly() throws InterruptedException:如果当前线程未被中断,则获取锁。如果锁可用,则获取锁,并立即返回。如果当前线程在获取锁时被 中断,并且支持对锁获取的中断,则将抛出InterruptedException,并清除当前线程的已中断状态。
中断线程的方法参照: https://blog.csdn.net/qq_32608881/article/details/107021335
例:
1 package com.test; 2 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 public class InterruptionInJava extends Thread { 7 Lock lock = new ReentrantLock(); 8 private volatile static boolean on = false; 9 10 public static void main(String[] args) throws InterruptedException { 11 Thread testThread = new Thread(new InterruptionInJava(), "t1"); 12 Thread testThread1 = new Thread(new InterruptionInJava(), "t2"); 13 testThread.start(); 14 testThread1.start(); 15 Thread.sleep(1000); 16 InterruptionInJava.on = true; 17 testThread.interrupt(); 18 } 19 20 @Override 21 public void run() { 22 try { 23 test(Thread.currentThread()); 24 } catch (InterruptedException e) { 25 e.printStackTrace(); 26 } 27 } 28 29 public void test(Thread thread) throws InterruptedException { 30 lock.lockInterruptibly(); 31 try { 32 System.out.println(thread.getName() + "获取了锁"); 33 while (!on) { 34 try { 35 Thread.sleep(10000000); 36 } catch (InterruptedException e) { 37 System.out.println(Thread.currentThread().getName() 38 + "被中断了"); 39 } 40 } 41 } finally { 42 lock.unlock(); 43 System.out.println(thread.getName() + "释放了锁"); 44 } 45 } 46 }
运行结果:
t1获取了锁
t2获取了锁
t1被中断了
t1释放了锁
(3)boolean tryLock():如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。
tryLolck()还能够实现可轮询查询,如果不能获得所有需要的锁,则可以使用轮询的获取方式重新获取控制权,它会释放已经获得的控制权,然后重新尝试。
例:
1 package com.test; 2 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantLock; 5 6 public class Main { 7 Lock lock = new ReentrantLock(); 8 private int i = 0; 9 10 public static void main(String[] args) { 11 final Main main = new Main(); 12 new Thread(new Runnable() { 13 public void run() { 14 try { 15 main.write(Thread.currentThread()); 16 } catch (InterruptedException e) { 17 e.printStackTrace(); 18 } 19 } 20 }).start(); 21 new Thread(new Runnable() { 22 public void run() { 23 try { 24 main.write(Thread.currentThread()); 25 } catch (InterruptedException e) { 26 System.out.println(Thread.currentThread().getName()+"线程被中断了"); 27 } 28 } 29 }).start(); 30 } 31 32 public void write(Thread thread) throws InterruptedException{ 33 if(lock.tryLock()) { 34 try { 35 System.out.println(thread.getName() + "获取了锁"); 36 Thread.sleep(5000); 37 } finally { 38 lock.unlock(); 39 System.out.println(thread.getName() + "释放了锁"); 40 } 41 }else { 42 System.out.println(thread.getName()+"当前锁不可用"); 43 } 44 } 45 }
运行结果:
Thread-0获取了锁 Thread-1当前锁不可用 Thread-0释放了锁
(4)boolean tryLock(long time, TimeUnit unit) throws InterruptedException:如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。
当使用内部锁时,一旦开始请求,锁就不能停止,所以内部锁实现具有时限的活动带来了风险,为了解决这一问题,可使用定时锁,当具有时限的活动调用阻塞方法,定时锁能够在时间预算内设定相应的超时,如果活动在期待的时间内没能获得结果,定时锁能使程序提前返回,可定时锁由boolean tryLock(long time, TimeUnit unit)实现。
例:
1 package com.test; 2 3 import java.util.concurrent.TimeUnit; 4 import java.util.concurrent.locks.Lock; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 public class Main { 8 Lock lock = new ReentrantLock(); 9 private int i = 0; 10 11 public static void main(String[] args) { 12 final Main main = new Main(); 13 new Thread(new Runnable() { 14 public void run() { 15 try { 16 main.write(Thread.currentThread()); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 } 21 }).start(); 22 new Thread(new Runnable() { 23 public void run() { 24 try { 25 main.write(Thread.currentThread()); 26 } catch (InterruptedException e) { 27 System.out.println(Thread.currentThread().getName()+"线程被中断了"); 28 } 29 } 30 }).start(); 31 } 32 33 public void write(Thread thread) throws InterruptedException{ 34 if(lock.tryLock(2000, TimeUnit.MILLISECONDS )) { 35 try { 36 System.out.println(thread.getName() + "获取了锁"); 37 Thread.sleep(1500); 38 } finally { 39 lock.unlock(); 40 System.out.println(thread.getName() + "释放了锁"); 41 } 42 }else { 43 System.out.println(thread.getName()+"当前锁不可用"); 44 } 45 } 46 }
运行结果:
Thread-1获取了锁 Thread-1释放了锁 Thread-0获取了锁 Thread-0释放了锁
(5)void unlock():释放锁。
例:把上例释放锁代码屏蔽掉
1 package com.test; 2 3 import java.util.concurrent.TimeUnit; 4 import java.util.concurrent.locks.Lock; 5 import java.util.concurrent.locks.ReentrantLock; 6 7 public class Main { 8 Lock lock = new ReentrantLock(); 9 private int i = 0; 10 11 public static void main(String[] args) { 12 final Main main = new Main(); 13 new Thread(new Runnable() { 14 public void run() { 15 try { 16 main.write(Thread.currentThread()); 17 } catch (InterruptedException e) { 18 e.printStackTrace(); 19 } 20 } 21 }).start(); 22 new Thread(new Runnable() { 23 public void run() { 24 try { 25 main.write(Thread.currentThread()); 26 } catch (InterruptedException e) { 27 System.out.println(Thread.currentThread().getName()+"线程被中断了"); 28 } 29 } 30 }).start(); 31 } 32 33 public void write(Thread thread) throws InterruptedException{ 34 if(lock.tryLock(2000, TimeUnit.MILLISECONDS )) { 35 try { 36 System.out.println(thread.getName() + "获取了锁"); 37 Thread.sleep(1500); 38 } finally { 39 //lock.unlock(); 40 //System.out.println(thread.getName() + "释放了锁");41 } 42 }else { 43 System.out.println(thread.getName()+"当前锁不可用"); 44 } 45 } 46 }
运行结果:
Thread-1获取了锁
Thread-0当前锁不可用
由于线程1没有释放锁,线程2在获取锁时时得不到锁的。
(6)Condition newCondition():返回绑定到此 Lock 实例的新 Condition 实
除以上方法外,ReentrantLock 还增加了一些高级功能,主要有以下3项:
(1)等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情,可中断特性对处理执行时间非常长的同步块很有帮助。
(2)公平锁:多个线程在等待同一个锁时必须按照申请锁的时间顺序来依次获得锁,而非公平锁不能保证这一点,在锁被释放时,任何一个等待锁的线程都有机会获得锁,synchronized中的锁时非公平的,ReentranLock默认情况下也是非公平的,但可以通过带布尔值的构造函数要求使用公平锁。
(3)锁绑定多个条件:指一个ReentrantLock对象可以同时绑定多个Condition对象,er在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含的条件,如果和多余一个的条件关联的时候,就不得不额外添加一个锁,而ReentrantLock则无需这样做,只需要多次调用newCondition()方法即可。
例,调用 Condition.await() 将在等待前以原子方式释放锁,并在等待返回前重新获取锁。
ReentrantLock 实现了Lock 接口。获得ReentrantLock 的锁与进入synchronized块具有相同的语义,释放 ReentrantLock 锁与退出synchronized 块有相同的语义。相比于 synchronized,ReentrantLock 提供了更多的灵活性来处理不可用的锁。
3.编程时锁的选择
1.最好既不是用 Lock/Condition 也不使用 synchronized关键字,在许多情况下,可以使用java.util.concurrent包中的一种机制,它会处理所有的加锁。
2.如果synchronized 关键字适合编写的程序,那就尽量使用它,这样可以减少编写的代码数量,减少出错的几率,如果特别需要Lock/Condition 结构提供的独有的特性时,才是用Lock/Condition 。
参考文献:
1. https://www.cnblogs.com/liuconglin/p/6693825.html#_label1_3
2.https://www.cnblogs.com/kylindai/archive/2006/01/24/322667.html
3.不明来历pdf文档《Java锁机制详解》,如有侵权,请联系LZ。