Java中的锁分类与应用
- Java锁的品种
在笔者面试过程时,常常会被问到各种各样的锁,如乐观锁、读写锁等等,十分繁多,在此做一个总结。介绍的内容如下:
- 乐观锁/乐观锁
- 独享锁/共享锁
- 互斥锁/读写锁
- 可重入锁
- 偏心锁/非偏心锁
- 分段锁
- 偏差锁/轻量级锁/重量级锁
- 自旋锁
以上是一些锁的名词,这些分类并不是全是指锁的状态,有的指锁的个性,有的指锁的设计,上面总结的内容是对每个锁的名词进行肯定的解释。
1.1 乐观锁/乐观锁
乐观锁与乐观锁并不是特指某两种类型的锁,是人们定义进去的概念或思维,次要是指对待并发同步的角度。
乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为他人不会批改,所以不会上锁,然而在更新的时候会判断一下在此期间他人有没有去更新这个数据,能够应用版本号等机制。乐观锁实用于多读的利用类型,这样能够进步吞吐量,在Java中java.util.concurrent.atomic包上面的原子变量类就是应用了乐观锁的一种实现形式CAS(Compare and Swap 比拟并替换)实现的。
乐观锁:总是假如最坏的状况,每次去拿数据的时候都认为他人会批改,所以每次在拿数据的时候都会上锁,这样他人想拿这个数据就会阻塞直到它拿到锁。比方Java外面的同步原语synchronized关键字的实现就是乐观锁。
乐观锁适宜写操作十分多的场景,乐观锁适宜读操作十分多的场景,不加锁会带来大量的性能晋升。
乐观锁在Java中的应用,就是利用各种锁。
乐观锁在Java中的应用,是无锁编程,经常采纳的是CAS算法,典型的例子就是原子类,通过CAS自旋实现原子操作的更新。
1.1.1 乐观锁
乐观锁总是认为不存在并发问题,每次去取数据的时候,总认为不会有其余线程对数据进行批改,因而不会上锁。然而在更新时会判断其余线程在这之前有没有对数据进行批改,个别会应用“数据版本机制”或“CAS操作”来实现。
(1) 数据版本机制
实现数据版本个别有两种,第一种是应用版本号,第二种是应用工夫戳。以版本号形式为例。
版本号形式:个别是在数据表中加上一个数据版本号version字段,示意数据被批改的次数,当数据被批改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若方才读取到的version值为以后数据库中的version值相等时才更新,否则重试更新操作,直到更新胜利。
外围SQL代码:
1 update table set xxx=#{xxx}, version=version+1 where id=#{id} and version=#{version};
(2) CAS操作
CAS(Compare and Swap 比拟并替换),当多个线程尝试应用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并能够再次尝试。
CAS操作中蕴含三个操作数——须要读写的内存地位(V)、进行比拟的预期原值(A)和拟写入的新值(B)。如果内存地位V的值与预期原值A相匹配,那么处理器会主动将该地位值更新为新值B,否则处理器不做任何操作。
1.2 乐观锁
乐观锁认为对于同一个数据的并发操作,肯定会产生批改的,哪怕没有批改,也会认为批改。因而对于同一份数据的并发操作,乐观锁采取加锁的模式。乐观的认为,不加锁并发操作肯定会出问题。
在对任意记录进行批改前,先尝试为该记录加上排他锁(exclusive locking)。
如果加锁失败,阐明该记录正在被批改,那么以后查问可能要期待或者抛出异样。具体响应形式由开发者依据理论须要决定。
如果胜利加锁,那么就能够对记录做批改,事务实现后就会解锁了。
期间如果有其余对该记录做批改或加排他锁的操作,都会期待咱们解锁或间接抛出异样。
1.2 独享锁/共享锁
独享锁是指该锁一次只能被一个线程所持有。
共享锁是指该锁可被多个线程所持有。
对于Java ReentrantLock而言,其是独享锁。然而对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其写锁是独享锁。
读锁的共享锁可保障并发读是十分高效的,读写,写读,写写的过程是互斥的。
独享锁与共享锁也是通过AQS来实现的,通过实现不同的办法,来实现独享或者共享。
对于Synchronized而言,当然是独享锁。
1.3 互斥锁/读写锁
下面讲的独享锁/共享锁就是一种狭义的说法,互斥锁/读写锁就是具体的实现。
互斥锁在Java中的具体实现就是ReentrantLock。
读写锁在Java中的具体实现就是ReadWriteLock。
1.4 可重入锁
可重入锁又名递归锁,是指在同一个线程在外层办法获取锁的时候,在进入内层办法会主动获取锁。说的有点形象,上面会有一个代码的示例。
对于Java ReetrantLock而言,从名字就能够看出是一个重入锁,其名字是Re entrant Lock 从新进入锁。
对于Synchronized而言,也是一个可重入锁。可重入锁的一个益处是可肯定水平防止死锁。
; "复制代码")
1 synchronized void setA() throws Exception{ 2 Thread.sleep(1000); 3 setB(); 4 } 5
6 synchronized void setB() throws Exception{ 7 Thread.sleep(1000); 8 }
; "复制代码")
下面的代码就是一个可重入锁的一个特点。如果不是可重入锁的话,setB可能不会被以后线程执行,可能造成死锁。
1.5 偏心锁/非偏心锁
偏心锁是指多个线程依照申请锁的程序来获取锁。
非偏心锁是指多个线程获取锁的程序并不是依照申请锁的程序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿景象。
对于Java ReetrantLock而言,通过构造函数指定该锁是否是偏心锁,默认是非偏心锁。非偏心锁的长处在于吞吐量比偏心锁大。
对于Synchronized而言,也是一种非偏心锁。因为其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何方法使其变成偏心锁。
1.6 分段锁
分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的模式来实现高效的并发操作。
咱们以ConcurrentHashMap来说一下分段锁的含意以及设计思维,ConcurrentHashMap中的分段锁称为Segment,它即相似于HashMap(JDK7和JDK8中HashMap的实现)的构造,即外部领有一个Entry数组,数组中的每个元素又是一个链表;同时又是一个ReentrantLock(Segment继承了ReentrantLock)。
当须要put元素的时候,并不是对整个hashmap进行加锁,而是先通过hashcode来晓得他要放在哪一个分段中,而后对这个分段进行加锁,所以当多线程put的时候,只有不是放在一个分段中,就实现了真正的并行的插入。
然而,在统计size的时候,可就是获取hashmap全局信息的时候,就须要获取所有的分段锁能力统计。
分段锁的设计目标是细化锁的粒度,当操作不须要更新整个数组的时候,就仅仅针对数组中的一项进行加锁操作。
1.7 偏差锁/轻量级锁/重量级锁
这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁降级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。
偏差锁是指一段同步代码始终被一个线程所拜访,那么该线程会主动获取锁。升高获取锁的代价。
轻量级锁是指当锁是偏差锁的时候,被另一个线程所拜访,偏差锁就会降级为轻量级锁,其余线程会通过自旋的模式尝试获取锁,不会阻塞,进步性能。
重量级锁是指当锁为轻量级锁的时候,另一个线程尽管是自旋,但自旋不会始终继续上来,当自旋肯定次数的时候,还没有获取到锁,就会进入阻塞,该锁收缩为重量级锁。重量级锁会让他申请的线程进入阻塞,性能升高。
1.8 自旋锁
在Java中,自旋锁是指尝试获取锁的线程不会立刻阻塞,而是采纳循环的形式去尝试获取锁,这样的益处是缩小线程上下文切换的耗费,毛病是循环会耗费CPU。
2.锁的应用
2.1 准备常识
2.1.1 AQS
AbstractQueuedSynchronized 形象队列式的同步器,AQS定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如罕用的ReentrantLock/Semaphore/CountDownLatch…
AQS保护了一个volatile int state(代表共享资源)和一个FIFO线程期待队列(多线程争用资源被阻塞时会进入此队列)。
state的拜访形式有三种:
1 getState() 2 setState() 3 compareAndSetState()
AQS定义两种资源共享形式:Exclusive(独占,只有一个线程能执行,如ReentrantLock)和Share(共享,多个线程可同时执行,如Semaphore/CountDownLatch)。
不同的自定义同步器争用共享资源的形式也不同。自定义同步器在实现时只须要实现共享资源state的获取与开释形式即可,至于具体线程期待队列的保护(如获取资源失败入队/唤醒出队等),AQS曾经在顶层实现好了。自定义同步器实现时次要实现以下几种办法:
1 isHeldExclusively():该线程是否正在独占资源。只有用到condition才须要去实现它。 2 tryAquire(int):独占形式。尝试获取资源,胜利则返回true,失败则返回false。 3 tryRelease(int):独占形式。尝试开释资源,胜利则返回true,失败则返回false。 4 tryAcquireShared(int):共享形式。尝试获取资源。正数示意失败;0示意胜利,但没有残余可用资源;负数示意胜利,且有残余资源。 5 tryReleaseShared(int):共享形式。尝试开释资源,如果开释后容许唤醒后续期待结点返回true,否则返回false。
以ReentrantLock为例,state初始化为0,示意未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。尔后,其余线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即开释锁)为止,其余线程才有机会获取该锁。当然,开释锁之前,A线程本人是能够反复获取此锁的(state会累加),这就是可重入的概念。但要留神,获取多少次就要开释多少次,这样能力保障state是能回到零态的。
再以CountDownLatch为例,工作分为N个子线程去执行,state为初始化为N(留神N要与线程个数统一)。这N个子线程是并行执行的,每个子线程执行完后countDown()一次,state会CAS减1。等到所有子线程都执行完后(即state=0),会unpark()主调用线程,而后主调用线程就会await()函数返回,持续后余动作。
一般来说,自定义同步器要么是独占办法,要么是共享形式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也反对自定义同步器同时实现独占和共享两种形式,如ReentrantReadWriteLock。
2.1.2 CAS
CAS(Compare and Swap 比拟并替换)是乐观锁技术,当多个线程尝试应用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其余线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并能够再次尝试。
CAS操作中蕴含三个操作数——须要读写的内存地位(V)、进行比拟的预期原值(A)和拟写入的新值(B)。如果内存地位V的值与预期原值A相匹配,那么处理器会主动将该地位值更新为新值B,否则处理器不做任何操作。无论哪种状况,它都会在CAS指令之前返回该地位的值(在CAS的一些非凡状况下将仅返回CAS是否胜利,而不提取以后值)。CAS无效地阐明了“我认为地位V应该蕴含值A;如果蕴含该值,则将B放到这个地位;否则,不要更改该地位,只通知我这个地位当初的值即可”。这其实和乐观锁的抵触查看+数据更新的原理是一样的。
JAVA对CAS的反对:
在JDK1.5中新增java.util.concurrent包就是建设在CAS之上的。绝对于synchronized这种阻塞算法,CAS是非阻塞算法的一种常见实现。所以java.util.concurrent包中的AtomicInteger为例,看一下在不应用锁的状况下是如何保障线程平安的。次要了解getAndIncrement办法,该办法的作用相当于++i操作。
; "复制代码")
1 public class AtomicInteger extends Number implements java.io.Serializable{ 2 private volatile int value; 3 public final int get(){ 4 return value; 5 }
6
7 public final int getAndIncrement(){ 8 for (;;){ 9 int current = get(); 10 int next = current + 1; 11 if (compareAndSet(current, next)) 12 return current; 13 } 14 } 15
16 public final boolean compareAndSet(int expect, int update){ 17 return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 18 } 19 }
; "复制代码")
2.2 实战
2.2.1 synchronized
synchronized可重入锁验证
; "复制代码")
1 public class MyLockTest implements Runnable { 2 public synchronized void get() { 3 System.out.println("2 enter thread name-->" + Thread.currentThread().getName()); 4 //reentrantLock.lock();
5 System.out.println("3 get thread name-->" + Thread.currentThread().getName()); 6 set();
7 //reentrantLock.unlock();
8 System.out.println("5 leave run thread name-->" + Thread.currentThread().getName()); 9 } 10
11 public synchronized void set() { 12 //reentrantLock.lock();
13 System.out.println("4 set thread name-->" + Thread.currentThread().getName()); 14 //reentrantLock.unlock();
15 } 16
17 @Override 18 public void run() { 19 System.out.println("1 run thread name-->" + Thread.currentThread().getName()); 20 get(); 21 } 22
23 public static void main(String[] args) { 24 MyLockTest test = new MyLockTest(); 25 for (int i = 0; i < 10; i++) { 26 new Thread(test, "thread-" + i).start(); 27 } 28 } 29
30 }
; "复制代码")
运行后果
; "复制代码")
1 1 run thread name-->thread-0
2 2 enter thread name-->thread-0
3 3 get thread name-->thread-0
4 1 run thread name-->thread-1
5 1 run thread name-->thread-2
6 4 set thread name-->thread-0
7 5 leave run thread name-->thread-0
8 1 run thread name-->thread-3
9 2 enter thread name-->thread-2
10 3 get thread name-->thread-2
11 4 set thread name-->thread-2
12 5 leave run thread name-->thread-2
13 2 enter thread name-->thread-1
14 3 get thread name-->thread-1
15 4 set thread name-->thread-1
16 5 leave run thread name-->thread-1
17 2 enter thread name-->thread-3
18 3 get thread name-->thread-3
19 4 set thread name-->thread-3
20 5 leave run thread name-->thread-3
21 1 run thread name-->thread-5
22 2 enter thread name-->thread-5
23 3 get thread name-->thread-5
24 4 set thread name-->thread-5
25 5 leave run thread name-->thread-5
26 1 run thread name-->thread-7
27 1 run thread name-->thread-6
28 2 enter thread name-->thread-7
29 3 get thread name-->thread-7
30 4 set thread name-->thread-7
31 1 run thread name-->thread-4
32 5 leave run thread name-->thread-7
33 1 run thread name-->thread-8
34 2 enter thread name-->thread-8
35 3 get thread name-->thread-8
36 4 set thread name-->thread-8
37 5 leave run thread name-->thread-8
38 1 run thread name-->thread-9
39 2 enter thread name-->thread-4
40 3 get thread name-->thread-4
41 4 set thread name-->thread-4
42 5 leave run thread name-->thread-4
43 2 enter thread name-->thread-6
44 3 get thread name-->thread-6
45 4 set thread name-->thread-6
46 5 leave run thread name-->thread-6
47 2 enter thread name-->thread-9
48 3 get thread name-->thread-9
49 4 set thread name-->thread-9
50 5 leave run thread name-->thread-9
; "复制代码")
get()办法中顺利进入了set()办法,阐明synchronized确实是可重入锁。剖析打印Log,thread-0先进入get办法体,这个时候thread-1、thread-2、thread-3期待进入,但当thread-0来到时,thread-2却先进入了办法体,没有依照thread-1、thread-2、thread-3的程序进入get办法体,阐明sychronized确实是非偏心锁。而且在一个线程进入get办法体后,其余线程只能期待,无奈同时进入,验证了synchronized是独占锁。
2.2.2 ReentrantLock
ReentrantLock既能够结构偏心锁又能够结构非偏心锁,默认为非偏心锁,将下面的代码改为用ReentrantLock实现,再次运行。
; "复制代码")
1 import java.util.concurrent.locks.ReentrantLock; 2
3 public class MyLockTest implements Runnable { 4
5 private ReentrantLock reentrantLock = new ReentrantLock(); 6
7 public void get() { 8 System.out.println("2 enter thread name-->" + Thread.currentThread().getName()); 9 reentrantLock.lock(); 10 System.out.println("3 get thread name-->" + Thread.currentThread().getName()); 11 set(); 12 reentrantLock.unlock(); 13 System.out.println("5 leave run thread name-->" + Thread.currentThread().getName()); 14 } 15
16 public void set() { 17 reentrantLock.lock(); 18 System.out.println("4 set thread name-->" + Thread.currentThread().getName()); 19 reentrantLock.unlock(); 20 } 21
22 @Override 23 public void run() { 24 System.out.println("1 run thread name-->" + Thread.currentThread().getName()); 25 get(); 26 } 27
28 public static void main(String[] args) { 29 MyLockTest test = new MyLockTest(); 30 for (int i = 0; i < 10; i++) { 31 new Thread(test, "thread-" + i).start(); 32 } 33 } 34
35 }
; "复制代码")
运行后果
; "复制代码")
1 1 run thread name-->thread-0
2 2 enter thread name-->thread-0
3 1 run thread name-->thread-1
4 2 enter thread name-->thread-1
5 3 get thread name-->thread-0
6 4 set thread name-->thread-0
7 1 run thread name-->thread-3
8 2 enter thread name-->thread-3
9 3 get thread name-->thread-3
10 4 set thread name-->thread-3
11 5 leave run thread name-->thread-3
12 1 run thread name-->thread-4
13 2 enter thread name-->thread-4
14 3 get thread name-->thread-4
15 4 set thread name-->thread-4
16 5 leave run thread name-->thread-4
17 1 run thread name-->thread-5
18 2 enter thread name-->thread-5
19 3 get thread name-->thread-5
20 4 set thread name-->thread-5
21 5 leave run thread name-->thread-5
22 1 run thread name-->thread-7
23 2 enter thread name-->thread-7
24 3 get thread name-->thread-7
25 4 set thread name-->thread-7
26 5 leave run thread name-->thread-7
27 5 leave run thread name-->thread-0
28 3 get thread name-->thread-1
29 4 set thread name-->thread-1
30 5 leave run thread name-->thread-1
31 1 run thread name-->thread-2
32 2 enter thread name-->thread-2
33 3 get thread name-->thread-2
34 4 set thread name-->thread-2
35 5 leave run thread name-->thread-2
36 1 run thread name-->thread-9
37 2 enter thread name-->thread-9
38 3 get thread name-->thread-9
39 4 set thread name-->thread-9
40 5 leave run thread name-->thread-9
41 1 run thread name-->thread-6
42 1 run thread name-->thread-8
43 2 enter thread name-->thread-8
44 3 get thread name-->thread-8
45 4 set thread name-->thread-8
46 5 leave run thread name-->thread-8
47 2 enter thread name-->thread-6
48 3 get thread name-->thread-6
49 4 set thread name-->thread-6
50 5 leave run thread name-->thread-6
; "复制代码")
确实如其名,可重入锁,当然默认确实是非偏心锁。thread-0持有锁期间,thread-1期待领有锁,当thread-0开释锁时thread-3先获取到锁,并非依照先后顺序获取锁的。
将其结构为偏心锁,看看运行后果是否合乎预期。查看源码结构偏心锁很简略,只有在结构器传入boolean值true即可。
; "复制代码")
1 /**
2 Creates an instance of {@code ReentrantLock} with the 3 given fairness policy. 4 5 @param fair {@code true} if this lock should use a fair ordering policy 6 */
7 public ReentrantLock(boolean fair) { 8 sync = fair ? new FairSync() : new NonfairSync(); 9 }
; "复制代码")
批改下面例程的代码构造方法为:
1 ReentrantLock reentrantLock = new ReentrantLock(true);
ReentrantLock实现偏心锁。
; "复制代码")
1 import java.util.concurrent.locks.ReentrantLock; 2
3 public class MyLockTest implements Runnable { 4
5 private ReentrantLock reentrantLock = new ReentrantLock(true);
6
7 public void get() { 8 System.out.println("2 enter thread name-->" + Thread.currentThread().getName()); 9 reentrantLock.lock(); 10 System.out.println("3 get thread name-->" + Thread.currentThread().getName()); 11 set(); 12 reentrantLock.unlock(); 13 System.out.println("5 leave run thread name-->" + Thread.currentThread().getName()); 14 } 15
16 public void set() { 17 reentrantLock.lock(); 18 System.out.println("4 set thread name-->" + Thread.currentThread().getName()); 19 reentrantLock.unlock(); 20 } 21
22 @Override 23 public void run() { 24 System.out.println("1 run thread name-->" + Thread.currentThread().getName()); 25 get(); 26 } 27
28 public static void main(String[] args) { 29 MyLockTest test = new MyLockTest(); 30 for (int i = 0; i < 10; i++) { 31 new Thread(test, "thread-" + i).start(); 32 } 33 } 34
35 }
; "复制代码")
运行后果
; "复制代码")
1 1 run thread name-->thread-0
2 2 enter thread name-->thread-0
3 3 get thread name-->thread-0
4 1 run thread name-->thread-2
5 2 enter thread name-->thread-2
6 4 set thread name-->thread-0
7 1 run thread name-->thread-3
8 2 enter thread name-->thread-3
9 1 run thread name-->thread-1
10 2 enter thread name-->thread-1
11 1 run thread name-->thread-5
12 2 enter thread name-->thread-5
13 3 get thread name-->thread-2
14 4 set thread name-->thread-2
15 5 leave run thread name-->thread-2
16 5 leave run thread name-->thread-0
17 3 get thread name-->thread-3
18 4 set thread name-->thread-3
19 5 leave run thread name-->thread-3
20 1 run thread name-->thread-9
21 2 enter thread name-->thread-9
22 3 get thread name-->thread-1
23 4 set thread name-->thread-1
24 5 leave run thread name-->thread-1
25 3 get thread name-->thread-5
26 4 set thread name-->thread-5
27 5 leave run thread name-->thread-5
28 3 get thread name-->thread-9
29 4 set thread name-->thread-9
30 5 leave run thread name-->thread-9
31 1 run thread name-->thread-6
32 2 enter thread name-->thread-6
33 3 get thread name-->thread-6
34 4 set thread name-->thread-6
35 1 run thread name-->thread-7
36 5 leave run thread name-->thread-6
37 2 enter thread name-->thread-7
38 3 get thread name-->thread-7
39 4 set thread name-->thread-7
40 5 leave run thread name-->thread-7
41 1 run thread name-->thread-4
42 2 enter thread name-->thread-4
43 3 get thread name-->thread-4
44 1 run thread name-->thread-8
45 2 enter thread name-->thread-8
46 4 set thread name-->thread-4
47 5 leave run thread name-->thread-4
48 3 get thread name-->thread-8
49 4 set thread name-->thread-8
50 5 leave run thread name-->thread-8
; "复制代码")
偏心锁在多个线程想要同时获取锁的时候,会发现再排队,依照先来后到的程序进行。
2.2.3 ReentrantReadWriteLock
读写锁的性能都会比排他锁要好,因为大多数场景读是多于写的。在读多于写的状况下,读写锁可能提供比排它锁更好的并发性和吞吐量。Java并发包提供读写锁的实现是ReentrantReadWriteLock。
个性
阐明
公平性抉择
反对非偏心(默认)和偏心的锁获取形式,吞吐量还是非偏心优于偏心
重进入
该锁反对重进入,以读写线程为例:读线程在获取了读锁之后,可能再次获取读锁。而写线程在获取了写锁之后可能再次获取写锁,同时也能够获取读锁
锁降级
遵循获取写锁、获取读锁再开释写锁的秩序,写锁可能降级成为读锁
; "复制代码")
1 import java.util.HashMap; 2 import java.util.Map; 3 import java.util.concurrent.locks.Lock; 4 import java.util.concurrent.locks.ReentrantReadWriteLock; 5
6 public class MyLockTest { 7
8 public static void main(String[] args) { 9 for (int i = 0; i < 10; i++) { 10 new Thread(new Runnable() { 11 @Override 12 public void run() { 13 Cache.put("key", new String(Thread.currentThread().getName() + " joke")); 14 } 15 }, "threadW-" + i).start(); 16 new Thread(new Runnable() { 17 @Override 18 public void run() { 19 System.out.println(Cache.get("key")); 20 } 21 }, "threadR-" + i).start(); 22 new Thread(new Runnable() { 23 @Override 24 public void run() { 25 Cache.clear(); 26 } 27 }, "threadC-" + i).start(); 28 } 29 } 30 } 31
32 class Cache { 33 static Map<String, Object> map = new HashMap<String, Object>(); 34 static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); 35 static Lock r = rwl.readLock(); 36 static Lock w = rwl.writeLock(); 37
38 // 获取一个key对应的value
39 public static final Object get(String key) { 40 r.lock(); 41 try { 42 System.out.println("get " + Thread.currentThread().getName()); 43 return map.get(key); 44 } finally { 45 r.unlock(); 46 } 47 } 48
49 // 设置key对应的value,并返回旧有的value
50 public static final Object put(String key, Object value) { 51 w.lock(); 52 try { 53 System.out.println("put " + Thread.currentThread().getName()); 54 return map.put(key, value); 55 } finally { 56 w.unlock(); 57 } 58 } 59
60 // 清空所有的内容
61 public static final void clear() { 62 w.lock(); 63 try { 64 System.out.println("clear " + Thread.currentThread().getName()); 65 map.clear(); 66 } finally { 67 w.unlock(); 68 } 69 } 70 }
; "复制代码")
运行后果
; "复制代码")
1 put threadW-0
2 clear threadC-1
3 put threadW-1
4 get threadR-1
5 threadW-1 joke 6 put threadW-2
7 get threadR-0
8 threadW-2 joke 9 clear threadC-0
10 get threadR-2
11 null
12 clear threadC-4
13 clear threadC-2
14 clear threadC-3
15 put threadW-4
16 put threadW-3
17 get threadR-3
18 threadW-3 joke 19 put threadW-5
20 get threadR-4
21 threadW-5 joke 22 clear threadC-5
23 put threadW-6
24 put threadW-7
25 get threadR-7
26 threadW-7 joke 27 get threadR-5
28 threadW-7 joke 29 get threadR-6
30 threadW-7 joke 31 clear threadC-6
32 clear threadC-7
33 put threadW-8
34 clear threadC-8
35 put threadW-9
36 get threadR-9
37 threadW-9 joke 38 clear threadC-9
39 get threadR-8
40 null
; "复制代码")
可看到一般HashMap在多线程中数据可见性失常。