java中的各种锁详细介绍,java锁的用法
本文给大家带来了一些关于java的知识,包括乐观锁、悲观锁、排他锁、共享锁等等。下面就来看看吧,希望对你有帮助。
乐观锁和悲观锁
悲观锁
悲观锁对应生活中的悲观主义者。悲观主义者总是想到事情会变坏。
举个生活中的例子,假设厕所只有一个坑。悲观锁厕所的时候会第一时间把门锁上,让别人上厕所只能在门外等着。这种状态是“阻塞的”。
回到代码世界,共享数据有一个悲观锁。每次线程要操作这个数据的时候,都会假设其他线程也可能操作这个数据,所以每次操作之前都会被锁死,所以其他线程只有拿不到锁才能操作这个数据。
在Java中,synchronized和ReentrantLock是典型的悲观锁,一些使用synchronized关键字的容器类,比如HashTable,也是悲观锁的应用。
乐观锁
乐观锁定对应的是生活中一个乐观的人,一个乐观的人总是认为事情会往好的方向发展。
举个生活中的例子,假设厕所只有一个坑。乐观锁定认为:前不着村后不着店人少,没人会抢我的坑。每次锁门都很浪费时间,不如不锁。看,看好锁定自然看好!
回到代码世界,乐观锁在操作时不会锁定数据,在更新时会判断其他线程是否会更新数据。
乐观锁可以通过使用版本号机制和CAS算法来实现。在Java语言中,java.util.concurrent.atomic包下的原子类是通过使用CAS乐观锁定实现的。
两种锁的使用场景
悲观锁和乐观锁没有优劣之分。他们有自己的适应场景。
乐观锁适用于写得少(冲突少)的场景,因为不需要加锁和释放锁,节省了锁的开销,从而提高了吞吐量。
如果是写的多读的少的场景,也就是冲突严重,线程之间的竞争有动机,使用乐观锁会导致线程不断重试,也可能降低性能,所以在这种场景下使用悲观锁更合适。
独占锁和共享锁
独占锁
排他锁意味着一个锁一次只能被一个线程持有。如果一个线程向数据添加了一个排他锁,那么其他线程可以向数据添加任何类型的锁。获得排他锁的线程可以读取和修改数据。
JDK中的Synchronized和java.util.concurrent(JUC)包中的锁的实现类都是排他锁。
共享锁
共享锁意味着一个锁可以被多个线程持有。如果一个线程将共享锁添加到数据中,那么其他线程只能将共享锁添加到数据中,并且可以添加独占锁。获取共享锁的线程只能读取数据,并且可以修改数据。
ReentrantReadWriteLock是JDK的一种共享锁。
互斥锁和读写锁
互斥锁
互斥锁是互斥锁的常规实现,即同一时刻只允许一个访问者访问一个资源,并且是唯一的、排他的。
互斥锁一次只能有一个线程有互斥锁,其他线程只能等待。
读写锁
读写锁是共享锁的具体实现。读写锁管理一组锁,一个是只读锁,一个是写锁。
当没有写锁时,读锁可以由多个线程同时持有,而写锁是独占的。写锁的优先级高于读锁,获得读锁的线程必须能够看到由之前释放的写锁更新的内容。
与互斥锁相比,读写锁具有更高程度的并发性。一次只有一个写线程,但是多个线程可以同时读。
JDK定义了一个读写锁的接口:读写锁。
公共接口读写锁{
/**
*获取读锁
*/
lock read lock();
/**
*获取写锁
*/
lock writeLock();
}ReentrantReadWriteLock实现读写锁接口。具体实现这里就不展开了,以后再分析源代码。
公平锁和非公平锁
公平锁
公平锁意味着多个线程按照申请锁的顺序获取锁。在这里,类似于排队买票。先来的人先买,后面的人排在队尾。这是公平的。
java中的构造函数可以初始化公平锁。
/**
*创建一个可重入锁,true表示公平锁,false表示不公平锁。默认不公平锁定
*/
Lock lock=new ReentrantLock(真);非公平锁
不公平锁是指多线程获取锁的顺序与申请锁的顺序不一致。稍后应用的线程可能比最先应用的线程具有获取锁的优先权。在高并发环境下,可能会造成优先级反转或饥饿(某个线程始终无法获得锁)。
在java中,synchronized关键字是一个不公平锁,ReentrantLock默认也是一个不公平锁。
/**
*创建一个可重入锁,true表示公平锁,false表示不公平锁。默认不公平锁定
*/
lock lock=new reentrant lock(false);
可重入锁
重入锁(recursive lock)又称递归锁,是指同一线程在外层方法中获取锁,内层方法自动获取锁。
对于Java ReentrantLock,它的名字可以看作是一个可重入锁。对于Synchronized,它也是一个可重入锁。
敲黑板:重入锁的一个好处是可以在一定程度上避免死锁。
以synchronized为例,看看下面的代码:
public synchronized void mehtodA()引发异常{
//做一些魔术
method b();
}
公共同步void mehtodB()引发异常{
//做一些魔术
}在上面的代码中,methodA调用methodB。如果一个线程调用methodA,然后调用methodB,它就不需要再次获取锁。这就是可重入锁的特点。如果不是重入锁,mehtodB可能不会被当前线程执行,这可能会导致死锁。
自旋锁
自旋锁是指线程在没有得到锁的情况下不直接挂起,而是执行一个繁忙的循环,这种循环称为自旋。
自旋锁的目的是降低线程被挂起的概率,因为线程的挂起和唤醒也是消耗资源的操作。
如果锁被另一个线程长时间占用,即使在自旋后,当前线程仍然会被挂起,忙循环就会变成浪费系统资源的操作,降低整体性能。因此,自旋锁不适合锁需要很长时间的并发情况。
在Java中,AtomicInteger类有spin的操作。让我们看一下代码:
public final int getAndAddInt(Object o,long offset,int delta) {
int v;
做{
v=getIntVolatile(o,offset);
} while(!compareAndSwapInt(o,offset,v,v delta));
回归v;
}如果}CAS操作失败,它将始终循环获取当前值并重试。
另外,自适应自旋锁也需要了解。
JDK1.6中引入了自适应spin,更加智能。旋转时间不再是固定的,而是由同一把锁上的最后一次旋转时间和锁的所有者的状态决定的。如果虚拟机认为这种旋转有可能再次成功,则需要更多的时间。如果旋转很少成功,则可以直接省略旋转过程,以避免浪费处理器资源。
分段锁
分段锁是一种锁的设计,不是特定的锁。
分段锁设计的目的是进一步细化锁的粒度。当操作不需要更新整个数组时,它只锁定数组中的一项。
在Java语言中,CurrentHashMap的底层使用段锁。与segment一起,它可以同时使用。
锁升级(无锁偏向锁轻量级锁重量级锁)
JDK1.6为了提高性能,减少获取和释放锁带来的消耗,引入了四种锁状态:无锁、偏锁、轻量锁和重量级锁。会随着多线程的竞争逐步升级,但不能降级。
无锁
无锁状态其实就是上面说的乐观锁定,这里就不赘述了。
偏向锁
Java偏向锁意味着它会偏向第一个访问锁的线程。如果在运行的进程中只有一个线程访问锁定的资源,并且没有多线程竞争,那么这个线程就不需要重复获取锁。在这种情况下,一个偏置锁将被添加到线程中。
偏置锁的实现是通过控制目标标志字的标志位来实现的。如果对象处于偏向状态,则需要进一步判断对象头中存储的线程ID是否与当前线程ID一致,如果一致,则直接输入。
轻量级锁
当线程竞争变得激烈时,偏向锁就会升级为轻量锁。轻量级锁认为虽然存在竞争,但竞争程度理想情况下是低的,它通过旋转来等待前一个线程释放锁。
重量级锁
如果线程并发进一步加剧,线程自旋超过一定次数,或者一个线程持有一个锁,一个线程自旋,第三个线程来访问(反正竞争继续增加),轻量级锁就会扩展成重量级锁,重量级锁会阻塞除此时拥有该锁的线程以外的所有线程。
升级到重量级锁其实就是互斥锁。当一个线程获得锁时,其余的线程将被阻塞并等待。
在Java中,synchronized关键字的内部实现原理是锁升级的过程:无锁-偏锁-轻量级锁-重量级锁。这个过程将在下面对同步关键词原理的解释中详细描述。
锁优化技术(锁粗化、锁消除)
锁粗化
锁定粗化是为了减少多个同步块的数量,扩大单个同步块的范围。本质上就是把多个加锁和解锁请求合并成一个同步请求。
比如一个循环体中有一个代码同步块,每个循环都会执行加锁和解锁操作。
私有静态最终对象锁=新对象();
for(int I=0;i 100i ) {
同步(锁定){
//做一些神奇的事情
}
}锁粗化后变成这样:
同步(锁定){
for(int I=0;i 100i ) {
//做一些神奇的事情
}
}锁消除
锁消除意味着虚拟机编译器在运行时检测到没有共享数据的竞争锁,然后消除这些锁。
下面举个例子让大家更好的理解。
公共字符串测试(字符串s1,字符串s2){
string buffer string buffer=new string buffer();
string buffer . append(S1);
string buffer . append(S2);
返回string buffer . tostring();
}上面的代码中有一个测试方法,主要用于串接字符串s1和字符串s2。
测试方法中有三个变量S1、S2和字符串缓冲区。都是局部变量。局部变量在堆栈上,堆栈是线程私有的,所以即使多个线程访问测试方法,它也是线程安全的。
我们都知道StringBuffer是线程安全的类,append方法是同步方法,但是test方法本来就是线程安全的。为了提高效率,虚拟机帮我们消除了这些同步锁,这个过程叫做锁消除。
StringBuffer.class
//append是同步方法。
公共同步StringBuffer append(String str) {
toStringCache=null
super . append(str);
还这个;
}:
一张图总结:
关于Java并发编程的知识很多,也是Java面试的高频考点。面试官必问,需要学习Java并发编程其他知识的朋友可以下载“阿里师兄总结的Java知识笔记共283页,超级详细”。
在Java语言的各种锁面前,最后用六个问题来概括:
推荐:以上《java视频教程》是图文详解!关于java锁的整理和总结的更多细节,请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。