1,公平锁与非公平锁
公平锁:指多个线程按照申请锁的顺序来获取锁类似排队打饭 先来后到
非公平锁:指在多线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取到锁,在高并发的情况下,有可能造成优先级反转或者饥饿现象
1.1 公平锁与非公平锁区别
- 并发包ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或者非公平锁,不填默认是非公平锁
- 区别
- 引申
ReentrantLock通过构造哈数指定该锁是否是公平锁 默认是非公平锁 非公平锁的优点在于吞吐量必公平锁大.
对于synchronized而言 也是一种非公平锁.
2, 可重入锁(又名递归锁)
- 指的是同一线程外层函数获得锁之后,内层递归函数仍然能获得该锁的代码,在同一个线程在外层方法获得锁的时候,在进入内层方法会自动获取锁。
- 也就是说,线程可以进入任何一个它已经拥有的锁所同步者的代码块。
- 其最大作用就是避免死锁。
- ReentrantLock/synchronized就是典型的可重入锁。
2.1 验证Demo1:
/**
* 可重入锁(也叫做递归锁)
* 指的是同一先生外层函数获得锁后,内层敌对函数任然能获取该锁的代码
* 在同一线程外外层方法获取锁的时候,在进入内层方法会自动获取锁
*
* 也就是说,线程可以进入任何一个它已经标记的锁所同步的代码块
*
* @author wangjie
* @version V1.0
* @date 2019/12/18
*/
public class ReenterLockDemo {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
try {
phone.sendSms();
} catch (Exception e) {
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try {
phone.sendSms();
} catch (Exception e) {
e.printStackTrace();
}
},"t2").start();
}
}
class Phone{
public synchronized void sendSms() throws Exception{
System.out.println(Thread.currentThread().getName()+"\tsendSms");
sendEmail();
}
public synchronized void sendEmail() throws Exception{
System.out.println(Thread.currentThread().getName()+"\tsendEmail");
}
}
- 运行结果:
2.2 Demo2:
/**
* 可重入锁(也叫做递归锁)Demo2
*
* @author wangjie
* @version V1.0
* @date 2019/12/18
*/
public class ReenterLockDemo2 {
public static void main(String[] args) {
Phone1 phone1 = new Phone1();
Thread t3 = new Thread(phone1);
Thread t4 = new Thread(phone1);
t3.start();
t4.start();
}
}
class Phone1 implements Runnable {
private Lock lock = new ReentrantLock();
@Override
public void run() {
get();
}
private void get() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\tget");
set();
} finally {
lock.unlock();
}
}
private void set() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "\tset");
} finally {
lock.unlock();
}
}
}
- 运行结果:
3,自旋锁
- 指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上线文切换的消耗,缺点是循环会消耗CPU
JUC包里的AtomicInteger类底层用CAS机制的实现就是一种自旋锁,以下是其底层核心类UnSafe的一段源码:
具体可查看:【JUC】 Java中的CAS
3.1 我们来自己实现一个自旋锁,Demo:
/**
* 手写一个自旋锁:循环比较知道成功为止
*
* @author wangjie
* @version V1.0
* @date 2019/12/18
*/
public class SpinLockDemo {
/**
* 原子引用线程
*/
AtomicReference<Thread> atomicReference = new AtomicReference();
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"\t come in");
while (!atomicReference.compareAndSet(null,thread)){
}
}
public void myUnLock(){
Thread thread = Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"\t in myUnLock()");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnLock();
},"AA").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
spinLockDemo.myLock();
spinLockDemo.myUnLock();
},"BB").start();
}
}
- 运行结果:
更多自旋锁相关:【锁】自旋锁-MCS/CLH队列
4,独占锁、共享锁,读写锁
读写锁Demo:
/**
* 多个线程同时操作 一个资源类没有任何问题 所以为了满足并发量
* 读取共享资源应该可以同时进行
* 但是
* 如果有一个线程想去写共享资源来 就不应该有其他线程可以对资源进行读或写
* @author wangjie
* @version V1.0
* @date 2019/12/18
*/
public class ReadWriteLockDemo {
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);
}, String.valueOf(i)).start();
}
for (int i = 1; i <= 5; i++) {
int finalI = i;
new Thread(() -> {
myCaChe.get(finalI + "");
}, String.valueOf(i)).start();
}
}
}
/**
* 资源类
*/
class MyCaChe {
/**
* 保证可见性
*/
private volatile Map<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
/**
* 写
*
* @param key
* @param value
*/
public void put(String key, Object value) {
reentrantReadWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t正在写入" + key);
//模拟网络延时
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t正在完成");
} finally {
reentrantReadWriteLock.writeLock().unlock();
}
}
/**
* 读
*
* @param key
*/
public void get(String key) {
reentrantReadWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "\t正在读取");
//模拟网络延时
try {
TimeUnit.MICROSECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t正在完成" + result);
} finally {
reentrantReadWriteLock.readLock().unlock();
}
}
public void clearCaChe() {
map.clear();
}
}
- 运行结果:
【完】
注:文章内所有测试用例源码:https://gitee.com/wjie2018/test-case.git