主要区别:
synchronized
是一个关键字,是在jvm层面上的,而Lock
是一个接口,实现他的锁都是以类的形式存在的。synchronized
获得锁和释放锁都是由虚拟机自动完成的,不需要用户去关心这些,执行完同步代码或者是在执行同步代码的时候出现异常都会释放锁,但是Lock
必须由用户自己显示的获取和释放锁。synchronized
是非公平的锁,在前面的线程获取锁的时候,后面的线程只能等待,直到前面的线程释放锁后面的下线程才有机会执行。synchronized
的实现都是虚拟机自己执行的,用户不能查看锁的状态,或者是无法判断出锁是否获取,而Lock
用户通过定义的方法可以查看锁是否获取到.synchronized
也是一种可重入锁,但是不能中断,是一种非公平的锁,Lock
也是可重入锁,可以判断锁是否获取,用户可以中断锁,用户还可以自定义公平策略。
在synchronized
的实现上,虚拟机会为加锁的对象维护一个变量,用来记录加锁和释放锁的次数,当一个对象获得锁的时候这个变量加一(原因synchronized
是一个可重入锁),当释放的时候,记录减一,直到记录变成0的时候,完全释放。
注意:以下是使用同步块的情况
package Practice;
/**
* Created with IDEA
*
* @author duzhentong
* @Date 2018/4/18
* @Time 15:40
*/
public class SynchronizedTest {
public static void main(String[] args) {
}
public void method(){
synchronized (this) {
System.out.println("方法开始执行!");
}
}
}
使用javap -c
命令查看字节码文件:
synchronized
映射成字节码指令就是增加来两个指令:monitorenter
和monitorexit
。当一条线程进行执行的遇到monitorenter
指令的时候,它会去尝试获得锁,如果获得锁那么锁计数+1(为什么会加一呢,因为它是一个可重入锁,所以需要用这个锁计数判断锁的情况),如果没有获得锁,那么阻塞。当它遇到monitorexit
的时候,锁计数器-1,当计数器为0,那么就释放锁。可以看见图中有两个monitorexit
,明明是只有一个monitorenter
指令,只记录了一次,为什么要执行两次monitorexit
,其实第二个monitorexit
并没有执行,在14行有一个goto指令,后面跟着22,说明指令执行到这里跳转到22行继续执行,第二个monitorexit
指令是程序发生异常的时候需要执行的。
Lock接口中定义的方法:
lock():获取锁,如果锁被占用则一直等待
unlock():释放锁
tryLock(): 返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true
tryLock(long time, TimeUnit unit):在限制时间内尝试获取锁,超过返回false
lockInterruptibly():获取锁定,除非当前线程是interrupted,获取锁,如果可用并立即返回。如果锁不可用,那么当前线程将等待
我们这里先讨论Lock的其中一个实现类ReentrantLock
,和synchronized
一样都是可重入锁。
ReentrantLock例子:
package Practice;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Created with IDEA
*
* @author duzhentong
* @Date 2018/4/18
* @Time 16:34
*/
public class LockTest {
public Lock lock = new ReentrantLock();
public void method(Thread thread) {
lock.lock();
try {
System.out.println("线程名"+thread.getName() + "获得了锁");
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("线程名"+thread.getName() + "释放了锁");
lock.unlock();
}
}
public static void main(String[] args) {
final LockTest lockTest = new LockTest();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.method(Thread.currentThread());
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lockTest.method(Thread.currentThread());
}
}, "t2");
t1.start();
t2.start();
}
}
输出结果:
线程t1获得了锁
线程t1释放了锁
线程t2获得了锁
线程t2释放了锁
从使用ReentrantLock
的代码上看,相比synchronized
,用户需要用代码来实现锁的获取与释放。
现在的synchronized
已经做了优化:在JDK1.6之后, 根据不同情形出现了偏向锁、轻量锁、对象锁,自旋锁(或自适应自旋锁)等,因此,现在的synchronized可以说是一个几种锁过程的封装。
可以参考这篇文章:https://www.cnblogs.com/xudilei/p/6840061.html
在日常编写代码的时候,还是提倡使用synchronized
,因为它还是比较简单就能实现的,而不是根据ReentrantLock
“性能会比较好”就倾向于使用ReentrantLock
,往往代码中的同步语句不可能会出现多么大的资源争用情况,在发现使用同步已经不能很好的解决并发问题的时候,再使用ReentrantLock
也是可以的。需要注意的是使用许多线程访问的公平锁的程序可能会比使用默认设置的非公平的整体吞吐量低,但获得锁的时间差异较小,并且可以保证等待过长时间。但注意,锁的公平性并不能保证线程调度的公平性
。