目录
睡觉前,突然有感而发,这世界就是一个unfairsync,而不是fairsync,于是想到了锁,总结一下部分锁的概念,具体的使用本人也在不断学习中,后期学习ok也会和大家分享
乐观锁
乐观锁,可能看名字不知道什么意思,说简单点就是,乐观锁,乐观的认为,自己需要的数据,不会被别人更改,但是为了保证安全,在更新的时候,还是会判断一个这个期间有没有别的线程更新这个数据,通俗点说就是,比较版本是否一致,最常用的:CAS机制
常用于读的场景
悲观锁
悲观锁,如同字面意思一样,总是向坏的方面去考虑,举个例子,有个人要停车,他总觉得车位别人会跟他抢,于是在使用的时候,他都会把车位锁上,这样别人就进不去,也就是所谓的阻塞
所以放在我们的开发中就是,悲观锁认为,每次它要拿的数据,都会被别的线程修改,所以它在拿数据的时候,就会上锁,在上锁期间,只有它自己可以使用访问,别的线程都会阻塞,最常用的:synchronize
常用于写的场景,读的效率低于乐观锁,但是在写的场景,先加锁可以保证数据的准确性
独占锁
就是说这个锁一次只能被一个对象持有
共享锁
指的是这个锁可以被多个线程同时持有使用
公平锁
见名知意,是一个公平的锁,好吧这是个废话
举个例子,有一个资源,n个线程同时访问,但是这个资源只支持一个线程去使用,当有一个线程在使用这个资源,别的线程就会排队,进入阻塞状态,当上一个线程使用完毕,就会按照排队顺序唤醒下一个等待的线程,说白了就是,先来后到,先到先得
优点:不会出现线程饿死的情况
缺点:整体的吞吐情况和效率相对于非公平锁比较低,且CPU要挨个唤醒所有被阻塞的线程,开销比较大
可以通过以下方式来获得公平锁
ReentrantLock fairsync = new ReentrantLock(true);
非公平锁
与公平锁对立,非公平锁,就是说不管先来后到,谁抢到谁牛逼,谁就用,就跟这个世界一样
举个例子,还是一个资源,n个线程,线程名从0到n,0线程抢到了资源的使用权,于是1线程到n线程全部阻塞,然后0线程使用完毕,这个时候,注意!0线程会继续参与到这个资源使用权的抢夺之中,而不会去排队,如果抢不到,再使用公平锁的方式,去排队,如果抢到了,就使用
优点:可以减少CPU唤醒线程的开销,整体吞吐量较高,因为线程有几率不阻塞就直接获得锁对资源进行使用
缺点:可能会出现线程饿死的情况,也可能某个线程很久很久很久才能获得锁
可以通过以下方式来获得锁,顺便一说,本人好奇看了看 ArrayBlockingQueue 和 LinkedBlockingDeque 的锁结构,发现默认都是非公平锁,且,连 ReentrantLock 默认的都是非公平锁
ReentrantLock unfairsync = new ReentrantLock(false);
互斥锁
在访问共享资源之前进行加锁,访问完成之后对共享资源进行解锁,对共享资源加锁之后,任何尝试对共享资源加锁的线程都会被阻塞,除非当前线程解锁,永远只有一个线程能够访问共享资源
读写锁
个人觉得,单独说读写锁不合适,应该把它和独占锁共享锁放一起更好理解,六个字,写独占,读共享,这也就有了两个特性,当你对这个读写锁添加读锁的,时候,你再添加写锁,就无法添加,会被阻塞,但是当你对这个读写锁添加写锁的时候,无论你添加读锁还是写锁,都会被阻塞
因此,读写锁更适合于多度少写的场景下,一定要注意,读写锁是一把锁,不是两把,别误会大家.....
不可重入锁
这个锁可能比较绕,举个例子
A方法调用了B方法,A方法被n个线程调用,A方法使用前加了锁,B方法本身也需要加锁,于是就出现了,A加锁,B也要加锁,两个人用的同一个锁对象,就出现了死锁,A方法要执行完需要B方法执行完释放锁,但是B方法执行完又需要A方法释放锁,这就是不可重入锁
可重入锁
可重入锁,就是为了解决上面的问题,其实实现可重入锁也很简单,核心就是加锁方,所谓解铃还须系铃人,锁也是,谁上的锁,肯定谁解锁
由于本人没有翻到可重入锁的源码,所以只能让本菜鸡来尝试实现一下这个锁
package own.study.auto;
public class MyLock {
boolean isLock = false;
int lockNum = 0;
Thread lockThread = null;
public synchronized void lock() throws InterruptedException {
// 判断是否已经上锁,如果已经上锁,并且不是当前调用 lock 申请上锁的线程,那就自旋
while (isLock && this.lockThread != Thread.currentThread()) {
this.wait();
}
// 没有走上面的自旋,说明没上锁,或者依旧是当前线程申请进行加锁,可以让进行二次加锁,记录是谁加的锁,加锁了几次
lockNum++;
isLock = true;
lockThread = Thread.currentThread();
}
public synchronized void unLock() {
// 如果加锁了,并且是当前线程加的锁,就进行加锁次数减一
if (isLock && this.lockThread == Thread.currentThread()) {
lockNum--;
// 判断一下加锁次数在减一之后是不是0,如果是0,那说明所有的锁都释放了,可以将标志位置为未加锁状态,同时唤醒下一个线程
if (lockNum == 0) {
isLock = false;
notify();
}
}
}
}
自旋锁
这个听起来是不是很高大上,那我告诉你,其实自旋锁的原理很多开发者都用过,比如,你消费MQ,是不是一个死循环,无限去读
我没跑题,因为自旋锁其实就是一个死循环,一直检测状态,比较浪费性能老实说
分段锁
严格来说,个人觉得分段锁更像是一种锁的设计思想,而不是一种锁,拿ConcurrentHashMap来说,默认16,采用分度锁,其实就是每一段看做一个独立的个体,进行加锁,用的时候你只需要去找自己用的那一段的锁就OK
最后,大家晚安喽~ 晚上十一点啦,到点睡觉,祝大家做个好梦 OvO~ 如果以上有说的不对的地方,请指正,多谢各位大佬们!