一、公平锁与非公平锁
二、可重入锁(递归锁)
三、自旋锁
四、独占锁(写锁)/共享锁(读锁)/互斥锁
公平锁: 多个线程按照申请锁的顺序来获取锁,类似排队打饭,队列,FIFO先来后到。
非公平锁: 多个线程获取锁顺序并不是按照申请锁的顺序来获取锁,有可能后申请的线程比先申请的线程优先获取锁。在高并发情况下可能造成优先级反转或饥饿现象。抢占式,抢不到才排队。
一个线程执行完毕,本该为优先级高的2号线程执行,但由于非公平锁,使得8号线程获取到锁
优先级低的反而频繁获取锁,造成其他线程因为没有获取到锁,而产生“饥饿”现象
非公平锁吞吐量比公平锁大
ReentrantLock默认非公平锁通过构造函数参数确定它为公平true or 非公平false,Synchronized为非公平锁
可重入锁(递归锁): 在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。
即: 线程可以进入任何一个它已经拥有的锁所同步着的代码块
/*目的:验证synchronized与ReentrantLock为可重入锁*/
//声明资源类Phone,
class Phone implements Runnable{
@Override
public void run() {
get();
}
// sendMess()与receiveMess()验证synchronized,其中sendMess()调用了receiveMess()
public synchronized void sendMess(){
System.out.println(Thread.currentThread().getName()+"\tsend message!");
receiveMess();
}
public synchronized void receiveMess() {
System.out.println(Thread.currentThread().getName()+"\treceive message!");
}
// get()与set()验证ReentrantLock,其中get()调用了set()
ReentrantLock lock = new ReentrantLock();
public void get(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"\tget!");
set();
}
finally {
lock.unlock();
}
}
public void set(){
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"\tset!");
}
finally {
lock.unlock();
}
}
}
public class ReentrantLockDemo{
public static void main(String[] args) throws InterruptedException {
Phone phone = new Phone();
//建立两个线程调用sendMess()
for (int i = 0; i < 2; i++) {
new Thread(()->{
phone.sendMess();
},"t"+String.valueOf(i)).start();
}
//建立两个线程调用get()
TimeUnit.SECONDS.sleep(2);
System.out.println();
System.out.println();
System.out.println();
Phone p1 = new Phone();
Phone p2 = new Phone();
Thread thread1 = new Thread(p1,"t3");
Thread thread2 = new Thread(p2,"t4");
thread1.start();
thread2.start();
}
}
结果:
观察结果发现,在synchronized修饰的sendMess()中调用synchronized修饰的receiveMess()竟然是同一个线程,也就说明了synchronized是一个可重入锁;
在get()中调用set()中使用ReentrantLock加锁后竟然是同一个线程调用,也就说明了ReentrantLock也是一个可重入锁。
项目 | synchronized | lock |
---|---|---|
原始结构 | jvm层面关键字 | API层面java.util.concurrent.locks.Lock |
使用方法 | 不需要用户手动释放锁,底层为monitorenter与monitorexit(1:2) | 需要用户手动释放锁,lock()与unlock()在try catch finally中使用 |
等待是否可中断 | 不可中断,除非抛出异常或者正常运行完成 | 可中断,设置超时方法tryLock(Long timeout,TimeUnit unit);lockInterruptibly(),调用interrupt()可中断 |
加锁是否公平 | 非公平锁 | 默认为非公平锁可传入true指定为公平锁 |
有无绑定多个条件的condition | 没有,要么随即唤醒一个线程notify(),要么唤醒全部线程notifyall() | 有,可以精确唤醒需要唤醒的线程condition.await(),condition.singall() |
结果:程序通过编译且可以运行,无法结束:由于缺少一次unlock()导致其他线程无法进行
结果:程序运行完成但抛出异常illegalMonitorStateException
1.次数unlock()=lock(),程序正常执行
2.次数unlock() 3.次数unlock()>lock(),程序可以通过编译,也可以正常运行,但抛出异常。 是指尝试获取锁的线程不会立即阻塞,而是 目的: 减少线程上下文频繁的切换而引起的性能损耗,所以才自旋让当前线程一直占用资源。 好处: 循环比较获取直到成功为止,没有类似wait()的阻塞 缺点: 是循环会消耗CPU资源 结果 独占锁(写锁):该锁一次只能被一个线程所持有 共享锁(读锁):该锁可被多个线程所持有, 互斥锁 ReentrantLock ReentrantReadWriteLock读共享锁,写独占锁 分布式缓存模拟:map实现 缓存三大基本操作: 业务要求:写操作 原子+独占 初始代码 结果: 我们可以用Lock,Synchronized与ReentrantReadWriteLock三、自旋锁
1.理论
采用循环的方式
去尝试获取锁。2.代码验证
①AtomicInteger实现原理CAS
atomicInteger.getAndIncrement();
public final int getAndIncrement() {
return U.getAndAddInt(this, VALUE, 1);
}
public final int getAndAddInt(Object o, long offset, int delta) {
int v;
do {
v = getIntVolatile(o, offset);
} while (!weakCompareAndSetInt(o, offset, v, v + delta));
return v;
}
②自己编写一个自旋锁
/*目的:验证自旋锁的处理机制*/
public class SpinLockDemo {
//实现一个有关于线程的原子类引用
AtomicReference atomicReference=new AtomicReference<Thread>();
//线程获取锁
public void mylock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"获得锁");
while(!atomicReference.compareAndSet(null,thread)){
}
}
//线程释放锁
public void myUnlock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName()+"释放锁");
atomicReference.compareAndSet(thread,null);
}
public static void main(String[] args) throws InterruptedException {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.mylock();
try {
TimeUnit.SECONDS.sleep(4);//占用锁4s
spinLockDemo.myUnlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"AAA").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
try {
spinLockDemo.mylock();
TimeUnit.SECONDS.sleep(4);//占用锁4s
spinLockDemo.myUnlock();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"BBB").start();
}
}
3.结论:自旋锁会使线程通过循环比较方式获取锁直到成功为止。
四、读写锁
1.理论
2.代码验证:模拟一个分布式缓存进行多线程读写操作
读,写,清空
整个过程必须是一个完整的统一体,不许被分割,被打断/*模拟一个分布式缓存读、写与清空操作*/
class MyCatch{
//缓存模拟
private volatile Map<String,Object> hashmap=new HashMap<>();
public void write(String key,Object value) throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"\t正在写入:"+key);//主要注重key的写入
TimeUnit.MILLISECONDS.sleep(300);//模拟网络延迟拥堵等情况
hashmap.put(key,value);
System.out.println(Thread.currentThread().getName()+"\t写入完成");
}
public void get(String key) throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"\t正在读取:"+key);//主要注重key的写入
TimeUnit.MILLISECONDS.sleep(300);//模拟网络延迟拥堵等情况
Object result = hashmap.get(key);
System.out.println(Thread.currentThread().getName()+"\t读取完成,读取值为:"+result);
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCatch myCatch = new MyCatch();
for (int i = 1; i <= 4; i++) {
final int tempInt=i;
new Thread(()->{
try {
myCatch.write(tempInt+"",Integer.valueOf(tempInt));
TimeUnit.MILLISECONDS.sleep(300);
myCatch.get(tempInt+"");
} catch (InterruptedException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
问题:发现不符合写操作的原子+独占
但是,前两者对于高并发读写操作有很大的性能负荷,不推荐。
代码:class MyCatch{
//缓存模拟
private volatile Map<String,Object> hashmap=new HashMap<>();
ReentrantReadWriteLock rwLock=new ReentrantReadWriteLock();
public void write(String key,Object value) throws InterruptedException {
try {
rwLock.writeLock().lock();
System.out.println(Thread.currentThread().getName()+"\t正在写入:"+key);//主要注重key的写入
TimeUnit.MILLISECONDS.sleep(300);//模拟网络延迟拥堵等情况
hashmap.put(key,value);
System.out.println(Thread.currentThread().getName()+"\t写入完成");
} catch (Exception e) {
e.printStackTrace();
}finally {
rwLock.writeLock().unlock();
}
}
public void get(String key) throws InterruptedException {
try {
rwLock.readLock().lock();
System.out.println(Thread.currentThread().getName()+"\t正在读取:"+key);//主要注重key的写入
TimeUnit.MILLISECONDS.sleep(300);//模拟网络延迟拥堵等情况
Object result = hashmap.get(key);
System.out.println(Thread.currentThread().getName()+"\t读取完成,读取值为:"+result);
} catch (Exception e) {
e.printStackTrace();
}
finally {
rwLock.readLock().unlock();
}
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCatch myCatch = new MyCatch();
for (int i = 1; i <= 4; i++) {
final int tempInt=i;
new Thread(()->{
try {
myCatch.write(tempInt+"",Integer.valueOf(tempInt));
TimeUnit.MILLISECONDS.sleep(300);
myCatch.get(tempInt+"");
} catch (InterruptedException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
}
3.结论:读写锁ReentrantReadWriteLock可以保证缓存中的读共享,写独占操作
学习三板斧:理论、代码、小总结,缓冲的三大基本操作:读、写,清空