公平锁和非公平锁
什么是公平锁
多个线程按照申请锁的顺序来获取锁,类似排队打饭 先来后到
什么是非公平锁
多个线程获取锁的顺序并不是按照锁的顺序,有可能后申请的线程比现申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或优先级饥饿现象
优先级反转
:A 线程先申请获取锁,B后申请,但是B优先获得锁,然后是A
优先级饥饿
:A、B、C 3个线程按照顺序申请获取锁,到快要执行B的时候,C加塞 获取B的锁资源,每次B要获取的时候,C都来加塞,导致B卡住一天了都没有执行
创建
juc包中,ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是非公平锁
synchronized也是非公平锁
公平锁和非公平锁的区别是什么
公平锁
:很公平,在并发环境中,每个线程在获取锁时会先查看次锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照 先进先出的规则从队列中取到自己
非公平锁
:比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁的方式
非公平锁的优点
:吞吐量大
可重入锁(又名递归锁)
什么是可重入锁
同一个线程在外层方法 A 获取锁的时候,在进入内层方法 B 会自动获取锁 ( A 和 B 都使用了sync关键词,或使用了ReentryLock )
---- 线程可以进入任何一个他已经拥有的锁 所同步着的代码块
可重入锁synchronized 示例
在Phone中的 call方法调用了另一个 sync 方法 sendMsg, 这个过程看下来 就相当于是 sendMsg方法 对于 call方法来说 sendMsg的所有代码都是写在 call() 方法中的一样,他俩共用一个线程执行
public class ReentryLockTest {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(() ->{
phone.call();
}, "aa").start();
new Thread(() ->{
phone.call();
}, "bb").start();
}
}
class Phone{
public synchronized void call(){
System.out.println(Thread.currentThread().getName() + ", 打电话");
this.sendMsg();
}
public synchronized void sendMsg(){
System.out.println(Thread.currentThread().getName() + ", 发短信");
}
}
执行结果如下
可重入锁 ReentryLock案例
public class ReentryLockTest {
public static void main(String[] args) {
Phone1 p1 = new Phone1();
Thread thread1 = new Thread(p1,"aa");
thread1.start();
Thread thread2 = new Thread(p1, "bb");
thread2.start();
}
}
class Phone1 implements Runnable{
private ReentrantLock reentrantLock = new ReentrantLock();
@Override
public void run() {
this.call();
}
public void call(){
reentrantLock.lock();
try {
System.out.println(Thread.currentThread().getName() + ", 调用call方法");
this.sendMse();
}finally {
reentrantLock.unlock();
}
}
public void sendMse(){
reentrantLock.lock();
try {
System.out.println(Thread.currentThread().getName() + ", 调用sendMsg方法");
}finally {
reentrantLock.unlock();
}
}
}
执行结果如下
可重入的锁的优点
避免了死锁:为什么说可重入锁避免了死锁:那sync举个例子:首先进入call方法,方法上锁,如果不是可重入锁的话,那么就不会进入 sendMsg方法,因为在外层已经上锁了,就不能在进入另一个方法了,就只能眼巴巴的等着,知道外层的这个锁解锁了之后,才能进入另一个方法
自旋锁
什么是自旋锁
尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁资源
/**
* 自旋锁 代码验证
* 首先什么是自旋锁:尝试获取锁的线程不会立即阻塞,而是采用循环获取的方式尝试获取锁
* 思路:
* A,B 2个线程一起操作一个共享资源,A线程1直占用着这个共享资源,B线程采用循环的方式尝试获取这个共享资源
* A 释放锁,此时 B 修改这个共享资源,然后打印输出结果
*/
public class SpinLockTest {
static AtomicReference<String> atomicReference = new AtomicReference<>();
public static void main(String[] args) {
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
//sout中的内容最好不要分2行,可能还没打印出第2行, 共享变量就已经被 bb 修改完了
System.out.println(atomicReference.compareAndSet(null, "over") + "现在的结果是"+ atomicReference.get());
System.out.println(Thread.currentThread().getName()+ ", aa 线程忙完了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}, "aa").start();
new Thread(()->{
try {
//停顿1s 的目的是为了确保上面那个线程先启动
TimeUnit.SECONDS.sleep(1);
System.out.println("bb线程开始执行");
while (!atomicReference.compareAndSet("over", null)){
System.out.println("bb 线程等待 aa 线程忙完");
}
System.out.println("bb线程修改完共享变量的值,现在的值为" + atomicReference.get());
}catch (Exception e){
e.printStackTrace();
}
}, "bb").start();
}
}
自旋锁的优缺点
优点
:其他线程不用进入阻塞状态,
缺点
:如果循环次数过多 消耗CPU 导致系统性能下降
独占锁(写锁) / 共享锁(读锁)
创建方式
独占锁,共享锁就是通常说的读写锁, 使用ReadWriteLock 接口种的 ReentrantReadWriteLock 创建
什么是 ReadWriteLock,ReadWriteLock 和 Lock的区别是什么,为什么要使用 ReadWriteLock 而不是 Lock
ReadWriteLock和Lock的区别
:
ReadWriteLock
:写操作的时候独占锁,读操作共享 可以不加锁,读读可共享,写读写写要独占锁
Lock
:写的时候只有1个人可以写,读的时候也只有1个人可以读( 读的时候1个人可以读不科学,读操作不会出现并发问题 应该允许多人读取,只有读和写同时存在的时候才会出现并发问题 )
案例1:
public class ReadWriteLockTest {
public static void main(String[] args) {
LockCache lockCache = new LockCache();
//创建5个线程, 5个线程读, 5个线程写
for (int i = 0; i < 5; i++) {
int finalI = i;
new Thread(() -> {
lockCache.write(String.valueOf(finalI), finalI);
}, "aa").start();
}
for (int i = 0; i < 5; i++) {
int finalI = i;
new Thread(() -> lockCache.read(String.valueOf(finalI)), "bb").start();
}
}
}
class LockCache {
/**
* 创建一个读写锁
*/
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
/**
* 共享资源
*/
private Map<String, Object> cache = Maps.newHashMap();
public void write(String key, Object obj) {
reentrantReadWriteLock.writeLock().lock();
System.out.println("开始写入 key: " + key + ", value: " + obj);
cache.put(key, obj);
System.out.println("写入完成");
reentrantReadWriteLock.writeLock().unlock();
}
public void read(String key) {
reentrantReadWriteLock.readLock().lock();
System.out.println("开始读取");
cache.get(key);
System.out.println("读取完成, value = " + cache.get(key));
reentrantReadWriteLock.readLock().unlock();
}
}
案例2
class ReadWriteCache {
private volatile Map map = Maps.newHashMap();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void read(Integer key) {
readWriteLock.readLock().lock();
System.out.println("开始读取");
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.get(key);
System.out.println("读取成功,值为:" + map.get(key));
readWriteLock.readLock().unlock();
}
public void write(Integer key, Integer value) {
readWriteLock.writeLock().lock();
System.out.println("开始写入:" + key);
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println("写入成功:");
readWriteLock.writeLock().unlock();
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
ReadWriteCache readWriteCache = new ReadWriteCache();
for (int i = 1; i <= 5; i++) {
int key = i;
int value = i;
new Thread(() -> {readWriteCache.write(key, value);}, String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
int key = i;
new Thread(() ->readWriteCache.read(key),String.valueOf(i)).start();
}
}
}