|
| 1 | +--- |
| 2 | +title: Java 锁 |
| 3 | +date: 2018/05/16 |
| 4 | +categories: |
| 5 | +- javase |
| 6 | +tags: |
| 7 | +- javase |
| 8 | +- concurrent |
| 9 | +- juc |
| 10 | +--- |
| 11 | + |
| 12 | +# Java 锁 |
| 13 | + |
| 14 | +> 本文内容基于 JDK1.8。 |
| 15 | +
|
| 16 | +<!-- TOC depthFrom:2 depthTo:3 --> |
| 17 | + |
| 18 | +- [概述](#概述) |
| 19 | + - [为什么会出现 Lock?](#为什么会出现-lock) |
| 20 | + - [synchronized](#synchronized) |
| 21 | +- [Lock 接口](#lock-接口) |
| 22 | + - [要点](#要点) |
| 23 | + - [源码](#源码) |
| 24 | +- [ReentrantLock](#reentrantlock) |
| 25 | + - [要点](#要点-1) |
| 26 | + - [源码](#源码-1) |
| 27 | + - [示例](#示例) |
| 28 | +- [ReadWriteLock](#readwritelock) |
| 29 | + - [要点](#要点-2) |
| 30 | + - [源码](#源码-2) |
| 31 | +- [ReentrantReadWriteLock](#reentrantreadwritelock) |
| 32 | + - [要点](#要点-3) |
| 33 | + - [示例](#示例-1) |
| 34 | +- [资料](#资料) |
| 35 | + |
| 36 | +<!-- /TOC --> |
| 37 | + |
| 38 | +## 概述 |
| 39 | + |
| 40 | +### 为什么会出现 Lock? |
| 41 | + |
| 42 | +### synchronized |
| 43 | + |
| 44 | +synchronized 是 Java 的内置锁。 |
| 45 | + |
| 46 | +如果一个代码块被 synchronized 修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况: |
| 47 | + |
| 48 | +1. 获取锁的线程执行完了该代码块,然后线程释放对锁的占有; |
| 49 | +2. 线程执行发生异常,此时 JVM 会让线程自动释放锁。 |
| 50 | + |
| 51 | +如果这个获取锁的线程被阻塞了,但是又没有释放锁,其他线程就只能一直等待。 |
| 52 | + |
| 53 | +使用 synchronized,如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。 |
| 54 | + |
| 55 | +可重入锁可中断锁公平锁读写锁 |
| 56 | + |
| 57 | +## Lock 接口 |
| 58 | + |
| 59 | +### 要点 |
| 60 | + |
| 61 | +### 源码 |
| 62 | + |
| 63 | +Lock 接口 |
| 64 | + |
| 65 | +```java |
| 66 | +public interface Lock { |
| 67 | + /** 获取锁,如果锁已被其他线程获取,则进行等待。 */ |
| 68 | + void lock(); |
| 69 | + /** |
| 70 | + * 获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。 |
| 71 | + * 当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁, |
| 72 | + * 而线程B只有在等待,那么对线程B调用threadB.interrupt()* 方法能够中断线程B的等待过程。 |
| 73 | + */ |
| 74 | + void lockInterruptibly() throws InterruptedException; |
| 75 | + /** |
| 76 | + * tryLock()方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回 true。 |
| 77 | + * 如果获取失败(即锁已被其他线程获取),则返回 false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。 |
| 78 | + */ |
| 79 | + boolean tryLock(); |
| 80 | + /** |
| 81 | + * 这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。 |
| 82 | + * 如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。 |
| 83 | + */ |
| 84 | + boolean tryLock(long time, TimeUnit unit) throws InterruptedException; |
| 85 | + /** 释放锁 */ |
| 86 | + void unlock(); |
| 87 | + /** |
| 88 | + * 返回绑定到此Lock实例的新的Condition实例。 |
| 89 | + * 在等待条件之前,锁必须由当前线程保持。对 Condition.await 的调用将在等待之前以原子方式释放锁, |
| 90 | + * 并在等待返回之前重新获取锁。 |
| 91 | + */ |
| 92 | + Condition newCondition(); |
| 93 | +} |
| 94 | +``` |
| 95 | + |
| 96 | +## ReentrantLock |
| 97 | + |
| 98 | +### 要点 |
| 99 | + |
| 100 | +作用:字面意为可重入锁。原理 |
| 101 | + |
| 102 | +### 源码 |
| 103 | + |
| 104 | +ReentrantLock 实现了 Lock 接口,所以支持 Lock 的所有方法。 |
| 105 | + |
| 106 | +#### 内部类 |
| 107 | + |
| 108 | +* `Sync` 这个类是 `ReentrantLock` 的同步控制核心。使用 AQS 状态来表示锁的保留数。 |
| 109 | +* `Sync` 是一个抽象类,有两个子类: |
| 110 | + * `FairSync` - 公平锁版本。 |
| 111 | + * `NonfairSync` - 非公平锁版本。 |
| 112 | + |
| 113 | +#### 重要属性 |
| 114 | + |
| 115 | +`Sync` 的实例。 |
| 116 | + |
| 117 | +```java |
| 118 | +private final Sync sync; |
| 119 | +``` |
| 120 | + |
| 121 | +#### 重要方法 |
| 122 | + |
| 123 | +**构造方法** |
| 124 | + |
| 125 | +```java |
| 126 | +// 默认初始化一个非公平的重入锁 |
| 127 | +public ReentrantLock() {} |
| 128 | +// 根据 boolean 值选择初始化一个公平的或不公平的重入锁 |
| 129 | +public ReentrantLock(boolean fair) {} |
| 130 | +``` |
| 131 | + |
| 132 | +**实现 Lock 接口的方法** |
| 133 | + |
| 134 | +以下方法的功能可以参考 [Lock](#lock-接口) 中的描述。具体实现完全基于 `Sync` 类中提供的方法。 |
| 135 | + |
| 136 | +```java |
| 137 | +void lock(); |
| 138 | +void lockInterruptibly() throws InterruptedException; |
| 139 | +boolean tryLock(); |
| 140 | +boolean tryLock(long time, TimeUnit unit) throws InterruptedException; |
| 141 | +void unlock(); |
| 142 | +Condition newCondition(); |
| 143 | +``` |
| 144 | + |
| 145 | +### 示例 |
| 146 | + |
| 147 | +```java |
| 148 | +public class ReentrantLockDemo { |
| 149 | + |
| 150 | + private ArrayList<Integer> arrayList = new ArrayList<Integer>(); |
| 151 | + private Lock lock = new ReentrantLock(); |
| 152 | + |
| 153 | + public static void main(String[] args) { |
| 154 | + final ReentrantLockDemo demo = new ReentrantLockDemo(); |
| 155 | + new Thread(() -> demo.insert(Thread.currentThread())).start(); |
| 156 | + new Thread(() -> demo.insert(Thread.currentThread())).start(); |
| 157 | + } |
| 158 | + |
| 159 | + private void insert(Thread thread) { |
| 160 | + lock.lock(); |
| 161 | + try { |
| 162 | + System.out.println(thread.getName() + "得到了锁"); |
| 163 | + for (int i = 0; i < 5; i++) { |
| 164 | + arrayList.add(i); |
| 165 | + } |
| 166 | + } catch (Exception e) { |
| 167 | + e.printStackTrace(); |
| 168 | + } finally { |
| 169 | + System.out.println(thread.getName() + "释放了锁"); |
| 170 | + lock.unlock(); |
| 171 | + } |
| 172 | + } |
| 173 | +} |
| 174 | +``` |
| 175 | + |
| 176 | +## ReadWriteLock |
| 177 | + |
| 178 | +### 要点 |
| 179 | + |
| 180 | +* 功能 |
| 181 | + * 对于特定的资源,ReadWriteLock 允许多个线程同时对其执行读操作,但是只允许一个线程对其执行写操作。 |
| 182 | +* 原理 |
| 183 | + * “读-读”线程之间不存在互斥关系。 |
| 184 | + * “读-写”线程、“写-写”线程之间存在互斥关系。 |
| 185 | + * ReadWriteLock 维护一对相关的锁。一个是读锁;一个是写锁。 |
| 186 | + * 将读写锁分开,有利于提高并发效率。 |
| 187 | + |
| 188 | +<p align="center"> |
| 189 | + <img src="https://raw.githubusercontent.com/dunwu/javase-notes/master/images/concurrent/ReadWriteLock.jpg"> |
| 190 | +</p> |
| 191 | + |
| 192 | +### 源码 |
| 193 | + |
| 194 | +ReadWriteLock 接口 |
| 195 | + |
| 196 | +```java |
| 197 | +public interface ReadWriteLock { |
| 198 | + /** |
| 199 | + * 返回用于读操作的锁 |
| 200 | + */ |
| 201 | + Lock readLock(); |
| 202 | + |
| 203 | + /** |
| 204 | + * 返回用于写操作的锁 |
| 205 | + */ |
| 206 | + Lock writeLock(); |
| 207 | +} |
| 208 | +``` |
| 209 | + |
| 210 | +## ReentrantReadWriteLock |
| 211 | + |
| 212 | +### 要点 |
| 213 | + |
| 214 | +* 功能 |
| 215 | + * ReentrantReadWriteLock 实现了 ReadWriteLock 接口,所以它是一个读写锁。 |
| 216 | + |
| 217 | +### 示例 |
| 218 | + |
| 219 | +``` |
| 220 | +public class ReentrantReadWriteLockDemo { |
| 221 | +
|
| 222 | + private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); |
| 223 | +
|
| 224 | + public static void main(String[] args) { |
| 225 | + final ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo(); |
| 226 | + new Thread(() -> demo.get(Thread.currentThread())).start(); |
| 227 | + new Thread(() -> demo.get(Thread.currentThread())).start(); |
| 228 | + } |
| 229 | +
|
| 230 | + public synchronized void get(Thread thread) { |
| 231 | + rwl.readLock().lock(); |
| 232 | + try { |
| 233 | + long start = System.currentTimeMillis(); |
| 234 | +
|
| 235 | + while (System.currentTimeMillis() - start <= 1) { |
| 236 | + System.out.println(thread.getName() + "正在进行读操作"); |
| 237 | + } |
| 238 | + System.out.println(thread.getName() + "读操作完毕"); |
| 239 | + } finally { |
| 240 | + rwl.readLock().unlock(); |
| 241 | + } |
| 242 | + } |
| 243 | +} |
| 244 | +``` |
| 245 | + |
| 246 | +## 资料 |
| 247 | + |
| 248 | +* [Java 并发编程实战](https://item.jd.com/10922250.html) |
| 249 | +* [Java 并发编程的艺术](https://item.jd.com/11740734.html) |
| 250 | +* http://www.cnblogs.com/dolphin0520/p/3923167.html |
0 commit comments