在面试的过程中有可能会问到:在Java并发编程中,锁有两种实现:使用隐式锁和使用显示锁,其中它们分别是什么?两者的区别是什么?所谓的显式锁和隐式锁的区别说的也是Synchronized和Lock的区别。
本文主要内容:将通过六个方面详细介绍sync和lock的区别。
synchronized是Java中的关键字,由JVM维护,是JVM层面的锁;
而lock是JDK5之后才出现的具体的类,使用Lock是调用对应的API,是API层面的锁。
synchronized的底层是通过monitorenter进行加锁的(底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖monito对象的,只有在同步块或者是同步方法中才可以调用wait/notify方法。因为只有在同步块或同步方法中,JVM才会调用monitor对象的。);通过monitorexit来退出锁的。
synchronized隐式锁;lock是显式锁
显氏锁和隐式锁的区别在于:使用显式锁的时候,使用者需要手动去获取和释放锁。
在使用synchronized关键字的时候,使用者不需要写其他的代码,程序就可以自动获取锁和释放锁。synchronized是由系统维护的,系统会自动的让程序释放占用的锁。
在使用lock的时候,需要使用者手动去获取锁和释放锁。如果没有释放锁,就可能出现死锁的现象。手动获取锁的方法lock.lock(); 释放锁的操作:unlock().
对于synchronized隐式锁使用
1、同步方法体,在方法声明中使用,如下:
public synchronized void method(){
//方法体
}
2、同步代码块,修饰在代码块外层,指定加锁对象,如下:
public void method2(){
synchronized (this) {
//一次只能有一个线程进入
}
}
上述synchronized(this)指定了当前对象本身作为锁,和它持有相同对象锁的地方将产生互斥性。当一个线程访问method2的同步代码块时,它就获得了这个object的对象锁。其他的线程对该object所有同步代码部分的访问都被暂时的阻塞。
sychronized的不同写法对程序响应的快慢和对资源高并发的利用程度不一样,性能和执行效率从差到优排序如下:
同步方法体 < 同步代码块 < 小对象锁同步代码块
小对象锁同步代码块指锁的对象的所占内存小,因为锁是对象,加锁和解锁都需要释放资源,那肯定是锁对象越小越好,实际应用如下:
private byte[] lock = new byte[1];
public void method3(){
synchronized (lock) {
//一次只能有一个线程进入
}
}
package comx.demo;
import java.io.ObjectOutputStream;
public class demo2_1 {
/**线程安全 1 同步代码块
*
* 线程同步:synchronized
*
* @param args
*/
public static void main(String[] args) {
//线程不安全
//解决方案1. 同步代码块
//格式; synchronnized(锁对象){
// }
//多态方式
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
private Object o = new Object();
@Override
public void run() {
// 出现不安全问题,,会出现负数
while(true){
//多个人对应一把锁,排队进行
//如果每个人对应一把自己的锁,则很可能会出现问题
synchronized (o) {
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
//休眠1秒,增大不安全概率
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票:" + count);
}else{
break;
}
}
}
}
}
}
package comx.demo;
public class demo2_2 {
/**线程安全 2 同步方法
*
* 线程同步:synchronized
*
* @param args
*/
public static void main(String[] args) {
//线程不安全
//解决方案2 . 同步方法
//格式; synchronnized(锁对象){
//
// }
//多态方式 创建一个任务
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
// private Object o = new Object();
@Override
public void run() {
// 出现不安全问题,,会出现负数
while(true){
Boolean flag = sale();
if (!flag){
break;
}
}
}
//增加一个修饰 synchronized 锁
public synchronized boolean sale(){
//this 关键字的使用
/**
* 使用
* synchronized(this){
*
* }
* 使用这种,其他的都无法执行这段代码,需要排队进行
*/
//如果为静态方法,则使用
// 类名.class
// Ticket.class
if (true) {
//卖票
System.out.println("正在准备卖票");
try {
//休眠1秒,增大不安全概率
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"出票成功,余票:" + count);
return true;
}
return false;
}
}
}
对于Lock类它的使用
package comx.demo;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class demo2_3 {
/**
* 同步代码块 和 同步方法 都属于隐式锁
* 线程同步: Lock
* @param args
*/
public static void main(String[] args) {
//线程不安全
//解决方案3 显式锁 Lock 子类 ReentranLock
Runnable run = new Ticket();
new Thread(run).start();
new Thread(run).start();
new Thread(run).start();
}
static class Ticket implements Runnable{
//票数
private int count = 10;
// 创建 显式锁 l
private Lock l = new ReentrantLock();
@Override
public void run() {
// 出现不安全问题,,会出现负数
while(true) {
//上锁(获取锁)
l.lock();
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
//休眠1秒,增大不安全概率
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("出票成功,余票:" + count);
}else{
break;
}
//关闭锁(释放锁)
l.unlock();
}
}
}
}
synchronized关键字是不可中断的,除非抛出异常或者正常运行结束
而Lock是可以中断的,中断方式:
调用设置超时方法tryLock(Long timeout,timeUnit unit)
调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断。
synchronized是非公平锁;
而lock两者都可以,默认创建是非公平锁。
注意:什么是公平锁,非公平锁?在这里解释一下:
公平锁:先来先到 ,排队,
非公平锁:线程一起抢
synchronized要么随机唤醒一个线程;要么是唤醒所有等待的线程。
lock:可以用实现分组唤醒需要唤醒的线程,可以精准的唤醒,而不是像synchronized那样,不能精准唤醒线程。
synchronized是托管给JVM去执行的,而Lock是Java写的控制锁的代码。在Java1.5中,synchronized的性能是低效的,因为其是重量级锁的操作,需要从用户态切换到内核态消耗大量的时间。相比之下使用Java提供的Lock对象,性能更高一些。但是到了Java1.6之后,对synchronized关键字进行了优化,有适应性自旋,锁消除,轻量级锁,偏向锁等,导致在Java1.6中synchronized的性能并不比lock差。
在之前的synchronized版本中使用是悲观锁的机制,即线程独占锁,其它线程只能依靠阻塞来等待线程释放的锁,而线程阻塞时会引起线程的上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。
而Lock使用的乐观锁的方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁的实现方式就CAS机制(compareAndSetstate),调用的是CPU提供的底层指令。
转载于:
【Java锁体系】五、隐式锁和显式锁的区别(Synchronized和Lock的区别)
显示锁与隐式锁的区别
隐式锁 Synchronized 与显示锁 Lock的用法和简单对比