锁
synchronized
可以加在代码块中,也可加到方法中
public synchronized void method(){
}
public static void main(String[] args){
synchronized(锁){
}
}
ReentrantLock
java
ReentrantLock lock = new ReentrantLock();
lock.lock();
//这里是主要逻辑的代码
lock.unlock();
Lock是一个接口,提供了一下几个方法
lock() //加锁
tryLock()//尝试加锁
tryLock(时间,时间单位)//尝试在指定时间内加锁,避免无限期阻塞
unlock()//解锁
Lock在使用的时候,需要手工加锁,手工解锁。不会自动释放锁。你的释放锁的代码必须放在finally当中进行。,以免因为其他原因导致无法执行到解锁逻辑
!IMPORTANT
- synchronized :
jdk一个语法
可以写在方法声明或者代码块中
都是可重入锁
会存在死锁的问题,发生死锁直接无解
不会进行优化,没有公平锁和读写锁的概念
只有非公平锁(除非你用线程调度)- ReentrantLock :
是一个类,是一个Lock的子类
只能写在代码中
都是可重入锁
必须手工加锁,手工解锁,保证加锁的次数和解锁的次数必须一样,否则锁可能无法释放
一定程度上能够解决死锁的问题
一定程度上能够优化为读写锁。
可以实现公平锁
非公平锁
多个线程之间抢cpu资源,抢到了就执行,抢不到就不执行
synchronized就是非公平锁,Reentrant默认是非公平锁,但是可以在new Reentrant( )时候传入参数true,此时就是公平锁
java
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i1 = 0; i1 < 3; i1++) {
synchronized (Test.class){
System.out.println(Thread.currentThread()+"吃了"+i1+"次");
}
}
}
}).start();
}
}
}
公平锁
每个线程轮流执行,每次都是让在线程上排队最久的那个线程来执行。
Java
public class Test {
public static void main(String[] args) {
//ReentrantLock默认非公平锁 true 公平锁 false 非公平锁
ReentrantLock lock = new ReentrantLock(true);
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i1 = 0; i1 < 3; i1++) {
lock.lock();//偷懒 为了然给你看到更清楚,我就不加finally了
System.out.println(Thread.currentThread()+"吃了"+i1+"次");
lock.unlock();
}
}
}).start();
}
}
}
死锁的解决
Mysql会自动释放其中一个锁。Java的不会自动释放。直接就锁死在这个位置了。
但是ReentrantLock能够响应中断。一定程度上解决死锁。
运行以下代码输出,线程1和线程2都把自己的锁加上了,此时线程1无法加上线程2的锁,报异常,最后程序不至于一直处于挂载状态
cmd
be locked lock1
be locked lock2
be locked lock2 by thread1false
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.base/java.util.concurrent.locks.ReentrantLock$Sync.tryRelease
be locked lock2 by thread1true
java
public class TestLock {
public static void main(String[] args) {
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
lock1.lock();;
System.out.println("be locked lock1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//线程1尝试给线程2加锁,发现加不了,报异常,最后程序不至于一直处于挂载状态
boolean success = lock2.tryLock();
System.out.println("be locked lock2 by thread1"+success);
lock1.unlock();
lock2.unlock();
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
lock2.lock();
System.out.println("be locked lock2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
boolean success = lock1.tryLock();
System.out.println("be locked lock2 by thread1"+success);
lock2.unlock();
lock1.unlock();
}
});
t1.start();
t2.start();
}
}
ReentrantReadWriteLock
读写锁,主要针对缓存,针对数据操作。
是一个实现类
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.writeLock().lock()
lock.writeLock().unlock()
lock.readLock().lock()
lock.readLock().unlock()
同时readlock,就同步执行,其余都是异步执行
synchronized和ReentrantLock都是互斥拍它锁。有些情况下,我们需要更细颗粒度的操作,希望:读读共享 读写互斥 写写互斥 。上面那两个锁就不满足要求了。
Java
public class TestReadWrite {
public static void main(String[] args) {
//读读共享 读写互斥 写写互斥
ReentrantReadWriteLock lock1 = new ReentrantReadWriteLock();
new Thread(new Runnable() {
@Override
public void run() {
//给锁1加锁
lock1.writeLock().lock();
System.out.println("线程1加上了锁1");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
lock1.writeLock().unlock();
System.out.println("线程1解锁");
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
lock1.writeLock().lock();
System.out.println("线程2加上了锁2");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
lock1.writeLock().unlock();
System.out.println("线程2解锁");
}
}).start();
}
}
分布式锁(以秒杀为例)
java
@Resource
private RedissonClient redissonClient;//Redisson 框架提供的分布式锁
RLock lock = redissonClient.getLock("lock");
lock.lock();
lock.unlock();
错误代码演示
1: pom.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.jiazhong</groupId>
<artifactId>buySilk</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>buySilk</name>
<description>buySilk</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>3.0.2</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>17</source>
<target>17</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.jiazhong.buysilk.BuySilkApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
2: application.yml
yaml
server:
port: 8080
3: BuyController
Java
package com.jiazhong.buysilk;
import jakarta.annotation.Resource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BuyController {
//过去 库存总是存在在数据库中
//把库存和卖出的都存在redis中
//导入redis的包了
@Resource
private RedisTemplate<String,Integer> redisTemplate;
//库存
//卖出去了几件
private static Integer successccount = Integer.parseInt("0");
@GetMapping("/init")
public String init(){
//设置redis中库存数量为5
redisTemplate.opsForValue().set("stockcount",5);
successccount = Integer.parseInt("0");
return "init success";
}
@GetMapping("/buy")
public String buy(){
//读取redis里面的库存。
//判断库存到底够不波
//库存-1
//把-1之后的值重新再放入redis
Integer stockcount = redisTemplate.opsForValue().get("stockcount");
stockcount = stockcount - 1;
if(stockcount<0){
return "已经卖光了,明天再来吧";
}
redisTemplate.opsForValue().set("stockcount",stockcount);
successccount++;
return "您已经购买成功了,龚售出"+successccount+"件";
}
@GetMapping("/show")
public String show(){
return "共售出"+successccount+"件";
}
}
好像看似没问题,但是底下一上Jmeter工具测试就直接有问题了,5个库存,直接卖出60(随机的,根据cpu调度决定)件。哪怕把上面的代码缓存AtomicInteger也是错误的。分析原因:
txt
线程 A 读取库存 = 5,还没来得及扣减写回;
线程 B 也读取到库存 = 5;
线程 A 扣减为 4,写回 Redis;
线程 B 也扣减为 4,覆盖了线程 A 的结果;
最终相当于两个请求都扣减了库存,但 Redis 只减了 1,实际却卖了 2 件,直接导致超卖。
那我要是加锁呢?
java
@GetMapping("/buy")
public String buy(){
synchronized (BuyController.class){
//读取redis里面的库存。
//判断库存到底够不波
//库存-1
//把-1之后的值重新再放入redis
Integer stockcount = redisTemplate.opsForValue().get("stockcount");
stockcount = stockcount - 1;
if(stockcount<0){
return "已经卖光了,明天再来吧";
}
redisTemplate.opsForValue().set("stockcount",stockcount);
successccount.incrementAndGet();
return "您已经购买成功了,龚售出"+ successccount.get()+"件";
}
}
看似问题解决了,但是实际开发过程中,服务器是分布式集群的呢?加本地锁只是在本服务器上加的,如果其他集群上还是报错,而且集群中每个节点的本地内存变量(如 static 变量)是独立的,修改一个节点的变量不会同步到其他节点。所以别用
private static Integer successccount = Integer.parseInt("0");
正确代码演示(分布式锁)
java
@RestController
public class BuyController {
@Resource
private RedisTemplate<String, Integer> redisTemplate;
@Resource
private RedissonClient redissonClient;
@GetMapping("test")
public String test(HttpServletRequest request) {
int localPort = request.getLocalPort();
return "你的访问来源的端口号是" + localPort;
}
@GetMapping("init")
public String init() {
redisTemplate.opsForValue().set("stockcount", 5);
redisTemplate.opsForValue().set("successcount",0);
return "init ok";
}
@GetMapping("buy")
public String buy() {
RLock lock = redissonClient.getLock("lock");//Redisson 框架提供的分布式锁
try {
lock.lock();
Integer stockcount = redisTemplate.opsForValue().get("stockcount");
Integer successccount = redisTemplate.opsForValue().get("successcount");
stockcount--;
if (stockcount < 0) {
return "已经卖光";
}
redisTemplate.opsForValue().set("stockcount",stockcount);
successccount++;
redisTemplate.opsForValue().set("successcount",successccount);
return "您已经购买成功了,龚售出" + successccount + "件";
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
}
@GetMapping("/show")
public String show() {
Integer successcount = redisTemplate.opsForValue().get("successcount");
return "共售出" + successcount + "件";
}
}
!WARNING
- 非共享资源用本地锁,跨 JVM 共享资源用「本地锁 + 分布式锁」(或仅分布式锁),避免过度使用分布式锁。
- 避免使用本地线程变量(ThreadLocal)传递跨 JVM 数据
- 共享变量(如计数器、配置、状态)必须存储在 Redis/MySQL/ZooKeeper 等分布式存储中。