并发编程--锁-介绍
- LOCK
- ReentrantLock
- Condition
- ReentrantReadWriteLock
1. LOCK(计时器)
介绍
- 从Java 5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock。
- 既然都可以通过synchronized来实现同步访问了,那么为什么还需要提供Lock?
理解:
- synchronized的缺陷
- 一如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况
- 1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
- 2)线程执行发生异常,此时JVM会让线程自动释放锁;
- 那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能等待,试想一下,这多么影响程序执行效率。
- 因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
LOCK 的特点
支持重入锁,嗅探锁定,多路分支等功能,Lock接口中每个方法的使用, 1-4是用来获取锁的,5方法是用来释放锁的。
1)lock() //lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待.
2)tryLock() //tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false.
3)tryLock(long time, TimeUnit unit) //和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间, 在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true
4)lockInterruptibly() 比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
5)unLock() 。
6)newCondition() LOCK 中的类似Object等待wait/通知notify的方法
7) isLocked() 是否锁定 锁定嗅探。
8) isFail() 是否是公平锁。
9) getQueueLength() 返回正在等待获得此锁定的线程数。
10) getWaitQueueLength() 返回等待与锁定相关的给定条件Condition的线程数.
11) hasQueueThread(Thread thread) 查询指定的线程是否等待此锁。
12) hasQueueThreads() 查询是否存在线程正在等待此锁。
13) hasWaiters() 查询是否存在啊线程正在等待与此锁定有关的condition条件。
2. ReentrantLock
ReentrantLock,意思是“可重入锁”。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法.
> 注意 重入锁,在需要进行同步的代码部分加上锁,最后一定要释放锁,不然你就蛋疼了。
1 public class MyReentrantLock { 2 private Lock lock = new ReentrantLock(); 3 4 public void method1(){ 5 try { 6 lock.lock(); 7 System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method1.."); 8 Thread.sleep(1000); 9 System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method1.."); 10 Thread.sleep(1000); 11 } catch (InterruptedException e) { 12 e.printStackTrace(); 13 } finally { 14 15 lock.unlock();//释放锁 16 } 17 } 18 19 public void method2(){ 20 try { 21 lock.lock(); 22 System.out.println("当前线程:" + Thread.currentThread().getName() + "进入method2.."); 23 Thread.sleep(2000); 24 System.out.println("当前线程:" + Thread.currentThread().getName() + "退出method2.."); 25 Thread.sleep(1000); 26 } catch (InterruptedException e) { 27 e.printStackTrace(); 28 } finally { 29 30 lock.unlock();//释放锁 31 } 32 } 33 34 public static void main(String[] args) { 35 36 final MyReentrantLock ur = new MyReentrantLock(); 37 Thread t1 = new Thread(new Runnable() { 38 @Override 39 public void run() { 40 ur.method1(); 41 ur.method2(); 42 } 43 }, "t1"); 44 45 t1.start(); 46 try { 47 Thread.sleep(10); 48 } catch (InterruptedException e) { 49 e.printStackTrace(); 50 } 51 } 52 }
执行结果:
当前线程:t1进入method1.. 当前线程:t1退出method1.. 当前线程:t1进入method2.. 当前线程:t1退出method2..
lock.newCondition()
Condition 的作用:newCondition() LOCK 中的类似Object等待wait/通知notify的方法。
示例:
1 public class MyCondition { 2 private Lock lock = new ReentrantLock(); 3 private Condition condition = lock.newCondition(); 4 5 public void method1(){ 6 try { 7 lock.lock(); 8 System.out.println("当前线程:" + Thread.currentThread().getName() + "进入等待状态.."); 9 Thread.sleep(3000); 10 System.out.println("当前线程:" + Thread.currentThread().getName() + "释放锁.."); 11 condition.await(); // Object wait 12 System.out.println("当前线程:" + Thread.currentThread().getName() +"继续执行..."); 13 } catch (Exception e) { 14 e.printStackTrace(); 15 } finally { 16 lock.unlock(); 17 } 18 } 19 20 public void method2(){ 21 try { 22 lock.lock(); 23 System.out.println("当前线程:" + Thread.currentThread().getName() + "进入.."); 24 Thread.sleep(3000); 25 System.out.println("当前线程:" + Thread.currentThread().getName() + "发出唤醒.."); 26 condition.signal(); //Object notify 27 } catch (Exception e) { 28 e.printStackTrace(); 29 } finally { 30 lock.unlock(); 31 } 32 } 33 34 public static void main(String[] args) { 35 final MyCondition uc = new MyCondition(); 36 Thread t1 = new Thread(new Runnable() { 37 @Override 38 public void run() { 39 uc.method1(); 40 } 41 }, "t1"); 42 Thread t2 = new Thread(new Runnable() { 43 @Override 44 public void run() { 45 uc.method2(); 46 } 47 }, "t2"); 48 49 t1.start(); 50 t2.start(); 51 } 52 }
输出结果:
1 当前线程:t1进入.. 2 当前线程:t1释放锁.. 3 当前线程:t2进入.. 4 当前线程:t2发出唤醒.. 5 当前线程:t1继续执行...
多个Condition
可以通过一个LOCK锁产生多个Condition进行线程间的交互。非常灵活,是的部分需要换新的线程唤醒,其他线程则继续等待通知。
示例:MyManyCondition.java
1 public class MyManyCondition { 2 3 private ReentrantLock lock = new ReentrantLock(); 4 private Condition c1 = lock.newCondition(); 5 private Condition c2 = lock.newCondition(); 6 7 public void m1(){ 8 try { 9 lock.lock(); 10 System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m1等待.."); 11 c1.await(); 12 System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m1继续.."); 13 } catch (Exception e) { 14 e.printStackTrace(); 15 } finally { 16 lock.unlock(); 17 } 18 } 19 20 public void m2(){ 21 try { 22 lock.lock(); 23 System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m2等待.."); 24 c1.await(); 25 System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m2继续.."); 26 } catch (Exception e) { 27 e.printStackTrace(); 28 } finally { 29 lock.unlock(); 30 } 31 } 32 33 public void m3(){ 34 try { 35 lock.lock(); 36 System.out.println("当前线程:" +Thread.currentThread().getName() + "进入方法m3等待.."); 37 c2.await(); 38 System.out.println("当前线程:" +Thread.currentThread().getName() + "方法m3继续.."); 39 } catch (Exception e) { 40 e.printStackTrace(); 41 } finally { 42 lock.unlock(); 43 } 44 } 45 46 public void m4(){ 47 try { 48 lock.lock(); 49 System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒.."); 50 c1.signalAll(); 51 } catch (Exception e) { 52 e.printStackTrace(); 53 } finally { 54 lock.unlock(); 55 } 56 } 57 58 public void m5(){ 59 try { 60 lock.lock(); 61 System.out.println("当前线程:" +Thread.currentThread().getName() + "唤醒.."); 62 c2.signal(); 63 } catch (Exception e) { 64 e.printStackTrace(); 65 } finally { 66 lock.unlock(); 67 } 68 } 69 70 public static void main(String[] args) { 71 72 73 final MyManyCondition umc = new MyManyCondition(); 74 Thread t1 = new Thread(new Runnable() { 75 @Override 76 public void run() { 77 umc.m1(); 78 } 79 },"t1"); 80 Thread t2 = new Thread(new Runnable() { 81 @Override 82 public void run() { 83 umc.m2(); 84 } 85 },"t2"); 86 Thread t3 = new Thread(new Runnable() { 87 @Override 88 public void run() { 89 umc.m3(); 90 } 91 },"t3"); 92 Thread t4 = new Thread(new Runnable() { 93 @Override 94 public void run() { 95 umc.m4(); 96 } 97 },"t4"); 98 Thread t5 = new Thread(new Runnable() { 99 @Override 100 public void run() { 101 umc.m5(); 102 } 103 },"t5"); 104 105 t1.start(); // c1 106 t2.start(); // c1 107 t3.start(); // c2 108 109 110 try { 111 Thread.sleep(2000); 112 } catch (InterruptedException e) { 113 e.printStackTrace(); 114 } 115 116 t4.start(); // c1 117 try { 118 Thread.sleep(2000); 119 } catch (InterruptedException e) { 120 e.printStackTrace(); 121 } 122 t5.start(); // c2 123 124 } 125 }
执行结果:
当前线程:t1进入方法m1等待.. 当前线程:t3进入方法m3等待.. 当前线程:t2进入方法m2等待.. 当前线程:t4唤醒.. 当前线程:t1方法m1继续.. 当前线程:t2方法m2继续.. 当前线程:t5唤醒.. 当前线程:t3方法m3继续..
3. ReentrantReadWriteLock
ReentrantReadWriteLock是Lock的另一种实现方式.
我们已经知道了ReentrantLock是一个排他锁,同一时间只允许一个线程访问,而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。相对于排他锁,提高了并发性。
在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,这时ReentrantReadWriteLock能够提供比排他锁更好的并发性和吞吐量。
ReentrantReadWriteLock支持以下功能:
支持公平和非公平的获取锁的方式;
支持可重入。读线程在获取了读锁后还可以获取读锁;写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁;
还允许从写入锁降级为读取锁,其实现方式是:先获取写入锁,然后获取读取锁,最后释放写入锁。但是,从读取锁升级到写入锁是不允许的;
读取锁和写入锁都支持锁获取期间的中断;
Condition支持。仅写入锁提供了一个 Conditon 实现;读取锁不支持 Conditon ,readLock().newCondition() 会抛出 UnsupportedOperationException;
示例:MyReentrantReadWriteLock.java
1 public class MyReentrantReadWriteLock { 2 3 private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(); 4 private ReadLock readLock = rwLock.readLock(); 5 private WriteLock writeLock = rwLock.writeLock(); 6 7 public void read(){ 8 try { 9 readLock.lock(); 10 System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..."); 11 Thread.sleep(3000); 12 System.out.println("当前线程:" + Thread.currentThread().getName() + "退出..."); 13 } catch (Exception e) { 14 e.printStackTrace(); 15 } finally { 16 readLock.unlock(); 17 } 18 } 19 20 public void write(){ 21 try { 22 writeLock.lock(); 23 System.out.println("当前线程:" + Thread.currentThread().getName() + "进入..."); 24 Thread.sleep(3000); 25 System.out.println("当前线程:" + Thread.currentThread().getName() + "退出..."); 26 } catch (Exception e) { 27 e.printStackTrace(); 28 } finally { 29 writeLock.unlock(); 30 } 31 } 32 33 public static void main(String[] args) { 34 35 final MyReentrantReadWriteLock urrw = new MyReentrantReadWriteLock(); 36 37 Thread t1 = new Thread(new Runnable() { 38 @Override 39 public void run() { 40 urrw.read(); 41 } 42 }, "t1"); 43 Thread t2 = new Thread(new Runnable() { 44 @Override 45 public void run() { 46 urrw.read(); 47 } 48 }, "t2"); 49 Thread t3 = new Thread(new Runnable() { 50 @Override 51 public void run() { 52 urrw.write(); 53 } 54 }, "t3"); 55 Thread t4 = new Thread(new Runnable() { 56 @Override 57 public void run() { 58 urrw.write(); 59 } 60 }, "t4"); 61 //注解1 62 // t1.start(); // R 63 // t2.start(); // R 64 //注解2 65 // t1.start(); // R 66 // t3.start(); // W 67 //注解3 68 t3.start(); // W 69 t4.start(); // W 70 } 71 }
运行结构(打开注解1):
当前线程:t2进入... 当前线程:t1进入... 当前线程:t2退出... 当前线程:t1退出...
运行结构(打开注解2):
当前线程:t1进入... 当前线程:t1退出... 当前线程:t3进入... 当前线程:t3退出...
运行结果(打开注解3):
当前线程:t3进入... 当前线程:t3退出... 当前线程:t4进入... 当前线程:t4退出...
总结:读读共享,读写互斥,写写互斥