简要讨论java中的锁
synchronized和lock的区别是什么?synchronized到底锁的是什么?
当java代码中存在操作同一个共享资源的时候,共享资源可以是一个变量,一个对象,或者redis缓存中的同一个key对应的值,或者其他缓存数据数据库,关系型数据库中同一条记录,或者消息中间件中同一个消息等等,都可能出现线程安全的问题,为了解决这种问题,java提供了自己的锁机制来确保线程安全,最常见的就是synchronized和lock锁。
两者的区别:
1.synchronized是托管给JVM执行的,底层实现是c++调用的本地方法,而lock是一个对象,通过AQS(abstract queue schronized)抽象的队列同步器实现的,它维护一个变量的状态,提供了一系列cas操作。
2.synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。
3.Lock用的是乐观锁方式。每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
4.ReentrantLock必须在finally中释放锁,否则后果很严重,编码角度来说使用synchronized更加简单,不容易遗漏或者出错。
5.ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁。
6.synchronized的话,锁的范围是整个方法或synchronized块部分;而Lock因为是方法调用,可以跨方法,灵活性更大。
ReentrantLock相对于synchronized多了三个高级功能:
1.等待可中断
在持有锁的线程长时间不释放锁的时候,等待的线程可以选择放弃等待.
tryLock(long timeout, TimeUnit unit)
2.公平锁
按照申请锁的顺序来一次获得锁称为公平锁.synchronized的是非公平锁,ReentrantLock可以通过构造函数实现公平锁.
new RenentrantLock(boolean fair)
公平锁和非公平锁。这2种机制的意思从字面上也能了解个大概:即对于多线程来说,公平锁会依赖线程进来的顺序,后进来的线程后获得锁。而非公平锁的意思就是后进来的锁也可以和前边等待锁的线程同时竞争锁资源。对于效率来讲,当然是非公平锁效率更高,因为公平锁还要判断是不是线程队列的第一个才会让线程获得锁。
3.绑定多个Condition
通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能.
详细的一些区别:
jdk1.6以前synchronized原始采用的是CPU悲观锁机制,它是个重量级锁,因为获取不到锁的线程会阻塞等待唤醒,这样会有线程的上下文切换,而线程的上下文切换由于涉及到操作系统内核态的转变,因此性能很低。
jdk1.6以后synchronized它变的轻量了,性能也好了很多,它是偏向锁-轻量级锁-重量级锁这样一步步进行锁升级的,而不是一上来就加个重量级锁。
当我们使用lock.lock()时,其内部主要是改变了一个变量状态,比如当两个线程使用lock获取锁时,比如下面的代码:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockTest{
private static boolean isLocked;
//lock方法
public synchronized void lock() throws InterruptedException {
if(isLocked){
wait();
}
isLocked=true;
}
//unlock方法
public synchronized void unLock(){
isLocked=false;
notify();
}
}
这是一个最简单的不可重入锁的实现代码,线程1调用lock方法改变了变量的值为true,相当于上了一把锁,线程2这时候再来获取这把琐时就被挂起了,直到线程1调用unLock方法后唤起线程2释放锁,线程2才能继续执行。
特别注意,上面只是一个举例,真正java Lock接口下面的锁不是让没获取到锁的线程挂起,而是自旋等待,例如reentrantlock的实现是通过AQS(abstract queue schronized)抽象的队列同步器实现的,AQS用一个int类型的变量表示同步状态,并提供了一系列的CAS操作来管理这个同步状态。
synchronized它不是一个对象,是一个java关键字啊,那么大家有没有思考过synchronized到底改变了什么?比如下面这段代码
public class SynchronizedTest {
private static final Object lock=new Object();
private static int i;
public void count(){
synchronized(lock){
i++;
}
}
}
synchronized(lock)到底改变了什么?
我们都知道synchronized是一个对象锁,那么synchronized到底改变了什么,要弄清楚这个问题必须先搞清楚java的对象布局。下图就是java的对象布局。
对象头里面存储了对象的锁状态,synchronized就是改变的对象头中锁的状态