Lock 接口
Synchronized 关键字回顾
概述
synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:
- 修饰一个代码块:被修饰的代码块称为同步语句块,其作用的范围是大括号{}
括起来的代码,作用的对象是调用这个代码块的对象; - 修饰一个方法:被修饰的方法称为同步方法,其作用的范围是整个方法,作用
的对象是调用这个方法的对象;- 虽然可以使用 synchronized 来定义方法,但 synchronized 并不属于方法定
义的一部分,因此, synchronized 关键字不能被继承。如果在父类中的某个方
法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这
个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上
synchronized 关键字才可以。当然,还可以在子类方法中调用父类中相应的方
法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,
子类的方法也就相当于同步了。
- 虽然可以使用 synchronized 来定义方法,但 synchronized 并不属于方法定
- 修改一个静态的方法:其作用的范围是整个静态方法,作用的对象是这个类的
所有对象; - 修改一个类:其作用的范围是 synchronized 后面括号括起来的部分,作用主
的对象是这个类的所有对象
优点:
- 使用synchronized修饰临界资源,从而避免了线程并发安全问题。
- 和Object类的 wait / notify组合使用,从而避免了死锁问题。
- 自动加解锁。
缺点:
- 同步锁,多线程间只能排队执行。
- 无法手动释放锁,当线程出现异常或阻塞时,其他线程只能不知期限地等待,影响程序效率。
代码示例
package com.avgrado.demo.thread;
class Ticket {
private int nums =30;
public synchronized void saleticket(){
if(nums>0){
System.out.println(Thread.currentThread().getName()+":卖出:"+(nums--)+"剩余:"+nums);
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for (int i = 0; i <15 ; i++) {
ticket.saleticket();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <19 ; i++) {
ticket.saleticket();
}
},"B").start();
new Thread(()->{
for (int i = 0; i <25 ; i++) {
ticket.saleticket();
}
},"C").start();
}
}
什么是Lock
概述
Lock
接口位于java.util.concurrent.locks
包中,是Java并发编程的一部分。Lock
接口提供了比使用synchronized
关键字更灵活的锁定机制。使用Lock
接口,可以更好地控制锁的获取和释放,以及处理尝试获取锁时的异常情况。
源码:
public interface Lock {
/**
* @Description 获取锁
*/
void lock();
/**
* @Description 若当前线程未被中断,则获取锁
* @throws InterruptedException
*/
void lockInterruptibly() throws InterruptedException;
/**
* @Description 仅在调用时,锁为空闲状态才获取锁
* @return
*/
boolean tryLock();
/**
* @Description 若锁在指定时间内是空闲状态,且当前线程未被中断才获取锁
* @param time
* @param unit
* @return
* @throws InterruptedException
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* @Description 释放锁
*/
void unlock();
/**
* @Description 获得 当前Lock对象的 condition 对象
* @return
*/
Condition newCondition();
}
Lock
接口中主要的方法有:
void lock()
: 获取锁。如果锁被其他线程持有,则当前线程将被阻塞,直到可以获取锁。void lockInterruptibly() throws InterruptedException
: 与lock()
方法类似,但如果当前线程在等待锁的过程中被中断,则会抛出InterruptedException
。boolean tryLock()
: 尝试非阻塞地获取锁。如果锁当前可用,则获取锁并返回true
;否则返回false
。boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
: 尝试在给定的时间内获取锁。如果在指定的时间内锁变得可用,则获取锁并返回true
;否则返回false
。如果在等待过程中线程被中断,则会抛出InterruptedException
。void unlock()
: 释放锁。如果当前线程不持有该锁,调用此方法将抛出IllegalMonitorStateException
。
使用Lock
接口时,通常需要使用try-finally
块来确保在使用完锁后释放锁,以避免发生死锁。例如:
Lock lock = new ReentrantLock();
try {
lock.lock();
// 访问共享资源
} finally {
lock.unlock();
}
Lock接口的实现类包括
ReentrantLock、
ReentrantReadWriteLock`等,这些实现类提供了不同的锁定策略以满足不同的并发需求
代码示例
package com.avgrado.demo.thread;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Ticket {
private int nums =30;
Lock lock = new ReentrantLock();
public void saleticket(){
lock.lock();
try{
if(nums>0){
System.out.println(Thread.currentThread().getName()+":卖出:"+(nums--)+"剩余:"+nums);
}
}finally {
lock.unlock();
}
}
}
public class ThreadDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for (int i = 0; i <15 ; i++) {
ticket.saleticket();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <19 ; i++) {
ticket.saleticket();
}
},"B").start();
new Thread(()->{
for (int i = 0; i <25 ; i++) {
ticket.saleticket();
}
},"C").start();
}
}
Lock 与的 Synchronized 区别
- 关联对象:synchronized 是 Java 内置的关键字,用于关联某个对象实例或类,实现对这个对象的锁定和解锁操作。而 Lock 是一个接口,需要显式地创建一个 Lock 对象来进行锁定和解锁操作。
- 可重入性:在 Java 中,synchronized 是可重入的,意味着如果一个线程已经获得了某个对象的锁,那么它可以再次获取该对象的锁而不会被阻塞。而 Lock 在默认情况下不是可重入的,但可以通过 ReentrantLock 类实现可重入锁。
- 锁的获取方式:synchronized 在获取锁时是隐式的,当线程进入 synchronized 保护的代码块时,会自动获取锁,并在退出代码块时释放锁。而 Lock 需要明确地调用 lock() 方法来获取锁,在使用完毕后需要调用 unlock() 方法来释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
- 条件变量:Lock 提供了 Condition 接口,可以通过 Condition 实现对线程的等待和唤醒操作,可以更灵活地控制线程。而在 synchronized 中,也可以使用 wait() 和 notify() 方法来实现类似的等待和唤醒操作,但是条件变量的使用更直观和灵活
- 性能:如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。
- 所属层面:synchronized关键字是属于JVM层面,底层是由monitorenter和monitorexit组成,monitorenter通过monitor对象来完成,其wait和notify等方法也依赖于monitor对象,只有在同步块或方法中才能调用 wait/notify 等方法;Lock是具体类(java.util.concurrent.locks.Lock),属于API层面的锁