Lock 是 java.util.concurrent (JUC) 包中的一个接口类。
锁是一种通过多个线程控制对共享资源的访问的工具。通常,一个锁提供对共享资源的独占访问:在一个时间只有一个线程可以获得锁和所有访问共享资源,需要先获得锁。然而,有些锁可以允许并发访问共享资源,如一个ReadWriteLock读锁。
Lock 和 Synchronized 都可以达到锁的目的,不过 Lock 有更灵活的结构,有完全不同的特性,可以支持多个相关的 Condition对象
package com.pzf.suo;
import lombok.AllArgsConstructor;
import java.util.concurrent.TimeUnit;
public class UnSync {
public static void main(String[] args) {
SaleTickets saleTicket = new SaleTickets(2);
new Thread(() -> saleTicket.sale(), "A").start();
new Thread(() -> saleTicket.sale(), "B").start();
new Thread(() -> saleTicket.sale(), "C").start();
}
}
@AllArgsConstructor
class SaleTickets {
private int num;
public void sale() {
if (num > 0) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"-> 当前票数" + (num--)+",剩余票数" + num);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
运行结果:
C-> 当前票数0,剩余票数-1
A-> 当前票数1,剩余票数0
B-> 当前票数2,剩余票数0
package com.pzf.suo;
import lombok.AllArgsConstructor;
import java.util.concurrent.TimeUnit;
public class ToSync {
public static void main(String[] args) {
SaleTickets1 saleTicket = new SaleTickets1(2);
new Thread(() -> saleTicket.sale(), "A").start();
new Thread(() -> saleTicket.sale(), "B").start();
new Thread(() -> saleTicket.sale(), "C").start();
}
}
@AllArgsConstructor
class SaleTickets1 {
private int num;
// synchronized 修饰非静态方法,是对调用该方法的对象加锁,俗称"对象锁"
public synchronized void sale() {
if (num > 0) {
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+"-> 当前票数" + (num--)+",剩余票数"+num);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
运行结果:
A-> 当前票数2,剩余票数1
C-> 当前票数1,剩余票数0
package com.pzf.suo;
import lombok.NoArgsConstructor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ToLock {
public static void main(String[] args) {
SaleTickets2 saleTicket = new SaleTickets2(5);
for (int i = 0; i < 10; i++) {
new Thread(() -> saleTicket.sale(), i + "").start();
}
}
}
@NoArgsConstructor
class SaleTickets2 {
private int num;
private Lock lock = new ReentrantLock();
public SaleTickets2(int num) {
this.num = num;
}
public void sale() {
lock.lock();
try {
if (num > 0) {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName()+"-> 当前票数"+(num--)+",剩余票数" + num);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 手动释放锁
lock.unlock();
}
}
}
运行结果:
0-> 当前票数5,剩余票数4
2-> 当前票数4,剩余票数3
1-> 当前票数3,剩余票数2
3-> 当前票数2,剩余票数1
4-> 当前票数1,剩余票数0
- Lock 是 java.util.concurrent (JUC) 包中的一个类,Synchronized 是一个内置的 java 关键字。
- Lock 可以判断是否获取到了锁,Synchronized 无法判断获取锁的状态。
- Lock 必须要手动释放锁,如果不释放锁会产生死锁。Synchronized 会自动释放锁。
- Lock 是可重入锁,默认为非公平锁,但是可以设置为公平锁。 Synchronized 可重入锁,不可中断的非公平锁。
- Lock 线程1(获得锁,阻塞)、线程2(不一定等待)。Synchronized 线程2 会等待下去。
- Lock 适合锁大量的同步代码,Synchronized 适合少量的代码同步问题。
公平锁
:每个线程获取锁的顺序是按照线程访问锁的先后顺序获取的,最前面的线程总是最先获取到锁。
非公平锁
:每个线程获取锁的顺序是随机的,并不会遵循先来先得的规则,所有线程会竞争获取锁。
两种锁的执行流程
- 公平锁:
获取锁时,先将线程自己添加到等待队列的队尾并休眠,当某线程用完锁之后,会去唤醒等待队列中队首的线程尝试去获取锁,锁的使用顺序也就是队列中的先后顺序,在整个过程中,线程会从运行状态切换到休眠状态,再从休眠状态恢复成运行状态,但线程每次休眠和恢复都需要从用户态转换成内核态,而这个状态的转换是比较慢的,所以公平锁的执行速度会比较慢。
- 非公平锁:
当线程获取锁时,会先通过 CAS 尝试获取锁,如果获取成功就直接拥有锁,如果获取锁失败才会进入等待队列,等待下次尝试获取锁。这样做的好处是,获取锁不用遵循先到先得的规则,从而避免了线程休眠和恢复的操作,这样就加速了程序的执行效率。
ReentrantLock 重入锁和 synchronize 关键字一样,是互斥锁。比synchronize关键字更加灵活。
ReentrantLock 默认为非公平锁,构造函数传 true 时,可设置为公平锁。
ReentrantReadWriteLock 主要特性 公平性 支持公平锁和非公平锁,默认是非公平锁,可以根据构造方式设置公平锁。
// 构造函数默认是false 非公平锁,由于读线程之间没有锁竞争,所以读操作,没有公平性和非公平性。
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock(true);
ReentrantReadWriteLock 实现类相关方法说明
package com.pzf.lock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 独占锁(写锁) 一次只能被一个线程占有
* 共享锁(读锁) 多个线程可以同时占有
*
* ReadWriteLock 实现类 ReentrantReadWriteLock
*
* 读-读 可以共存
* 读-写 不能共存
* 写-写 不能共存
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
// 普通写
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {myCache.put(temp + "", temp + "");}, String.valueOf(i)).start();
}
// 普通读
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {myCache.get(temp + "");}, String.valueOf(i)).start();
}
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println();
MyLockCache myLockCache = new MyLockCache();
// 读写锁 -> 写
for (int i = 6; i <= 10; i++) {
final int temp = i;
new Thread(() -> {myLockCache.put(temp + "", temp + "");}, String.valueOf(i)).start();
}
// 读写锁 -> 读
for (int i = 6; i <= 10; i++) {
final int temp = i;
new Thread(() -> {myLockCache.get(temp + "");}, String.valueOf(i)).start();
}
}
}
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
public void put(String key, Object value) {
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "写入成功");
}
public Object get(String key) {
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "开始读");
Object value = map.get(key);
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "读成功");
return value;
}
}
class MyLockCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "开始写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "写入成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public Object get(String key) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "开始读");
Object value = map.get(key);
System.out.println(Thread.currentThread().getName() + "-> key = " + key + "读成功");
return value;
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
return null;
}
}
2-> key = 2开始写入
2-> key = 2写入成功
1-> key = 1开始写入
5-> key = 5开始写入
5-> key = 5写入成功
3-> key = 3开始写入
3-> key = 3写入成功
4-> key = 4开始写入
4-> key = 4写入成功
1-> key = 1开始读
1-> key = 1读成功
1-> key = 1写入成功
2-> key = 2开始读
2-> key = 2读成功
3-> key = 3开始读
4-> key = 4开始读
4-> key = 4读成功
5-> key = 5开始读
3-> key = 3读成功
5-> key = 5读成功
7-> key = 7开始写入
7-> key = 7写入成功
10-> key = 10开始写入
10-> key = 10写入成功
6-> key = 6开始写入
6-> key = 6写入成功
8-> key = 8开始写入
8-> key = 8写入成功
9-> key = 9开始写入
9-> key = 9写入成功
6-> key = 6开始读
6-> key = 6读成功
10-> key = 10开始读
8-> key = 8开始读
7-> key = 7开始读
8-> key = 8读成功
9-> key = 9开始读
9-> key = 9读成功
10-> key = 10读成功
7-> key = 7读成功