可重入锁
我们来看什么是可重入锁
可重入锁的核心意思 是同一个线程在外层方法获取锁的时候 在进入内层方法会自动获取锁
这里我们分别举synchronized 和 ReentrantLock的例子
synchronized是可重入锁
package com.robot;
class Phone{
//外层方法
public synchronized void sendMes() {
System.out.println(Thread.currentThread().getName()+"\t sendMes");
call();//进入内层方法会自动获取锁
}
//内层方法也别synchronized修饰
public synchronized void call() {
System.out.println(Thread.currentThread().getName()+"\t call");
}
}
public class Demo1 {
public static void main(String[] args) {
Phone phone=new Phone();
new Thread(()->{
phone.sendMes();
},"t1").start();
}
}
//控制台结果
t1 sendMes
t1 call
ReentrantLock是可重入锁
package com.zyk;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Phone{
//两个方法持有同一个lock
Lock lock=new ReentrantLock();//默认是非公平锁
//外层方法
public void sendMes(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t sendMes");
//外层方法调用内层方法 自动获取锁
call();
}finally {
lock.unlock();
}
}
//内层方法被lock锁锁住
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t call");
}finally {
lock.unlock();
}
}
}
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone phone=new Phone();
new Thread(()->{
phone.sendMes();
},"A").start();
}
}
结果
A sendMes
A call
自旋锁
是指尝试获取锁的线程不会立即阻塞 而是采用循环的方式去尝试获取锁
我们来模拟一种自旋的情况
package com.zyk;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLockDemo {
//原子引用 引用的是线程
AtomicReference<Thread> atomicReference=new AtomicReference<>();
public void myLock(){
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"进来了");
while(!atomicReference.compareAndSet(null,thread)){
}
}
public void myUnlock(){
Thread thread=Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"离开了");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo=new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
spinLockDemo.myUnlock();
},"AAA").start();
//确保AAA线程先拿到锁
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(()->{
spinLockDemo.myLock();
spinLockDemo.myUnlock();
},"BBB").start();
}
}
结果
AAA进来了
BBB进来了
AAA离开了
BBB离开了
这块我们具体分析一下这个上述的过程
AAA首先获得了锁 —》等待3秒之后 BBB进入这个myLock方法 在while循环这里 CAS比较不满足条件 (因为此时真实值不是null)就一直在这里执行自旋过程—》再过7秒之后 AAA释放锁—》BBB拿到锁 结束自旋–》
注意
BBB进来了之后 并没有拿到锁 在这7秒钟之内 会一直自旋尝试获取
如果代码是这样
while(!atomicReference.compareAndSet(null,thread)){
System.out.println(Thread.currentThread().getName()+"在这里自旋");
}
就会发现 在BBB3秒钟这个时刻进来之后 到第10秒的这7秒内 控制台会打印满屏的在这里自旋
读写锁
独占锁 (读锁) 该锁一次只能被一个线程锁持有 ReentrantLock和synchronized都是独占锁
共享锁 (写锁) 该锁可以被多个线程锁持有
我们分析一个具体生活案例 在机场的显示屏上显示着各个航班的信息 那么 我们底下围着一圈人去看这个信息 这就好比共享锁 (肯定不能一个个人去排队看)这样就提高了我们的并发性 但是在某个航班延误的时候 只能有机场的工作人员去进行修改 这就好比写锁 如果这时候每个人每个线程都去写 呢这就直接乱了套了
我们先看一个没有加锁的错误示例
package com.zyk;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
class MyCache{
private volatile Map<String,Object> map=new HashMap<>();
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()+"正在写入"+key);
try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"写完了");
}
public void get(String key){
System.out.println(Thread.currentThread().getName()+"正在读取");
try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
Object result=map.get(key);
System.out.println(Thread.currentThread().getName()+"读完了"+result);
}
}
public class ReadAndWriteLockDemo {
public static void main(String[] args) {
MyCache myCache=new MyCache();
for (int i = 1; i <=5 ; i++) {
final int temp=i;
new Thread(()->{
myCache.put(temp+"",temp+"");
}).start();
}
for (int i = 1; i <=5 ; i++) {
final int temp=i;
new Thread(()->{
myCache.get(temp+"");
}).start();
}
}
}
这个时候我们如果使用了传统lock锁 呢么虽然数据安全性得到保证 但是并发的性能就会大大的下降 这是juc为我们提供了一种解决方式ReentrantReadWriteLock
我们这里在写的时候将会使用到 reentrantReadWriteLock.writeLock().lock();这个方法
我么这里在读的时候将会使用到reentrantReadWriteLock.readLock().lock();这个方法
package com.zyk;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class MyCache {
//写入类似缓存的东西用volatile修饰 保证可见性 第一时间拿到数据
private volatile Map<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
reentrantReadWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始写" +key);
try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写完了");
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantReadWriteLock.writeLock().unlock();
}
}
public void get(String key) {
reentrantReadWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "开始读");
try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "读结束" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantReadWriteLock.readLock().unlock();
}
}
}
public class ReadAndWriteLockDemo {
public static void main(String[] args) {
MyCache myCache=new MyCache();
for (int i = 1; i <=5 ; i++) {
final int temp=i;
new Thread(()->{
myCache.put(temp+"",temp+"");
}).start();
}
for (int i = 1; i <=5 ; i++) {
final int temp=i;
new Thread(()->{
myCache.get(temp+"");
}).start();
}
}
}
这里的结果就既保证了数据一致性 又保证了并发性 达到了我们满意的效果
nice 收工了 !!
晚安 好梦 希望大家心想事成