6.各种锁
1.公平锁和非公平锁
1.公平锁:
是值多个线程按照申请锁的顺序获取锁,类似排队打饭,先来后到
2.非公平锁:
是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获得锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象
上来直接尝试占有锁,如果尝试失败,再采用类似公平锁的那种方式
非公平锁的优点在于吞吐量比公平锁大
1.synchronize默认是非公平锁
2.Lock lock=new ReentrantLock(true);不传值默认是非公平锁,设置true为公平锁
2.可重入锁(又名递归锁)
可重入锁(又叫做递归锁)
可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁
1.指的是同一线程外层函数获得锁之后,内层递归函数仍然能都获取该锁的代码
2.在同一线程在外层方法获取锁的时候,进入内层方法会自动获取锁
也就是说:线程可以进入任何一个它已经用有锁所同步着的代码块
synchronize/ReentrantLock就是典型的非公平的可重入锁
可重入锁的最大优点就是避免死锁!
代码如下:
class Phone {
//重点1:外部加锁方法
public synchronized void sendMessagge() {
System.out.println(Thread.currentThread().getId() + ":发短信...");
//重点3:调用内部加锁方法
call();
}
//重点2:内部加锁方法
public synchronized void call() {
System.out.println(Thread.currentThread().getId() + ":打电话...");
}
}
3.自旋锁
Unsafe类+CAS思想(自旋)
是指尝试获取锁的线程不会阻塞,而是采用循环的方式尝试获取锁,这样的好处就是减少线程上下文切换的消耗,缺点就是循环会消耗CPU
自己写一个自旋锁:重点是CAS理念和原子对象引用的compareAndSet
1.自定义的锁(自旋)
class Mylock {
//重点1:创建原子引用,泛型时Thread,不传值时,默认引用类型的是null
AtomicReference<Thread> atomic = new AtomicReference<>()
//重点1:加锁方法
public void mylock() {
//重点3:获取进入当前方法的线程
Thread thread = Thread.currentThread();
//重点4:拿出公共内存中原始值(null)和自己的预期值(null)做比较,相同,则将主内存中的改为thread(当前线程),即占用锁了(使用非-->跳出while循环),
//其他线程进来,不满足条件,非,为true,不停的在while里循环,直到占用线程将其改为null
while (!atomic.compareAndSet(null, thread)) {
try {
System.out.println(Thread.currentThread().getName() + ":锁已经被占用了,过1秒钟再来查看...");
//重点5:这边加了休眠
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+":占用锁!");
}
//重点6:解锁方法
public void myUnlock() {
//重点7:获取当前进入方法的线程
Thread thread = Thread.currentThread();
//重点8:当主内存中的线程和进入的线程一致,则将主内存的线程置为null,即解锁,这时加锁的线程就可以拿到锁了
while (atomic.compareAndSet(thread, null)) {
System.out.println(Thread.currentThread().getName() + ":解锁..");
}
}
}
测试方法:
for (int i = 0; i < 3; i++) {
new Thread(()->{
try {
//重点1:这边加了锁,导致其他线程无法获得锁,不停的在while里循环,直至解锁
lock.mylock();
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"调用自己想要的业务方法...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.myUnlock();
}
},"线程"+i).start();
}
输出:
线程0:占用锁!
线程2:锁已经被占用了,过1秒钟再来查看...
线程1:锁已经被占用了,过1秒钟再来查看...
线程0调用自己想要的业务方法...
线程0:解锁..
线程2:占用锁!
线程1:锁已经被占用了,过1秒钟再来查看...
线程2调用自己想要的业务方法...
线程2:解锁..
线程1:占用锁!
线程1调用自己想要的业务方法...
线程1:解锁..
4.独占锁(写锁)/共享锁(读锁)/互斥锁
1.独占锁:指该锁只能被一个线程锁持有。对ReentrantLock和Synchronized而言都是独占锁
2.共享锁:指该锁可被多个线程所持有
3.对ReentrantReadWriteLock其读锁是共享锁,其写锁是独占锁
4.读锁的共享锁可以保证并发读是非常高效的,读写,写读,写写的过程是互斥的
样例代码如下:
//重点1:自定义缓存类
class Mycache {
//重点2:使用volatile修饰map(保证可见性,不保证原子性,禁止指令重排)map,作为缓存
private volatile Map<String, Object> map = new HashMap<>();
//重点3:定义读写锁
private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
//重点4:读写锁的写锁上锁(独占锁,其他的读线程写线程均无法操作)
rwLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "准备放入值..." + key + ":" + value);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "放入值成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
//重点5:读写锁读锁解锁
rwLock.writeLock().unlock();
}
}
public void get(String key) {
//重点6:读写锁---读锁加锁(读线程不受影响,不能进来写线程,即读写互斥)
rwLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "准备读取:key->" + key);
Thread.sleep(200);
Object object = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取结束!" + key + ":" + object);
} catch (Exception e) {
e.printStackTrace();
} finally {
//重点7读写锁--读锁解锁(读写线程开始争抢cpu资源)
rwLock.readLock().unlock();
}
}
}
public class ReadWriteLockDemo {
public static void main(String[] args) {
Mycache mycache = new Mycache();
//测试:启动10个线程分别放入和取出
for (int i = 0; i < 10; i++) {
final int temp = i;
new Thread(() -> {
mycache.put("key:" + temp, "value" + temp);
}, "--->写线程" + i).start();
new Thread(() -> {
mycache.get("key:" + temp);
}, "读线程" + i).start();
}
}
输出:
--->写线程0准备放入值...key:0:value0
--->写线程0放入值成功!
读线程0准备读取:key->key:0
读线程0读取结束!key:0:value0
--->写线程1准备放入值...key:1:value1
--->写线程1放入值成功!
--->写线程2准备放入值...key:2:value2
--->写线程2放入值成功!
....
读线程8准备读取:key->key:8
读线程9准备读取:key->key:9
读线程9读取结束!key:9:null
读线程8读取结束!key:8:value8
...
结论:
1.写写/写读互斥:如果有个线程正在写,其他线程均不能操作
2.读写互斥:如果有个线程正在读,读线程不影响,但是写线程不能操作
3.读读共享:多个读线程可以同时操作