欢迎光临
我们一直在努力

锁.................

synchronized

可以加在代码块中,也可加到方法中

public synchronized void method(){

}

public static void main(String[] args){
synchronized(锁){

}
}

ReentrantLock

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,此时就是公平锁

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();
}
}
}

公平锁

每个线程轮流执行,每次都是让在线程上排队最久的那个线程来执行。

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的锁,报异常,最后程序不至于一直处于挂载状态

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

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都是互斥拍它锁。有些情况下,我们需要更细颗粒度的操作,希望:读读共享 读写互斥 写写互斥 。上面那两个锁就不满足要求了。

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();
}
}

分布式锁(以秒杀为例)

@Resource
private RedissonClient redissonClient;//Redisson 框架提供的分布式锁
RLock lock = redissonClient.getLock("lock");

lock.lock();
lock.unlock();

错误代码演示

1: pom.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

server:
port: 8080

3: BuyController

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也是错误的。分析原因:

线程 A 读取库存 = 5,还没来得及扣减写回;
线程 B 也读取到库存 = 5;
线程 A 扣减为 4,写回 Redis;
线程 B 也扣减为 4,覆盖了线程 A 的结果;
最终相当于两个请求都扣减了库存,但 Redis 只减了 1,实际却卖了 2 件,直接导致超卖。

那我要是加锁呢?

@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");

正确代码演示(分布式锁)

@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 等分布式存储中。
  • 赞(0)
    未经允许不得转载:171主机测评 » 锁.................
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址