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

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

  1. 非共享资源用本地锁,跨 JVM 共享资源用「本地锁 + 分布式锁」(或仅分布式锁),避免过度使用分布式锁。
  2. 避免使用本地线程变量(ThreadLocal)传递跨 JVM 数据
  3. 共享变量(如计数器、配置、状态)必须存储在 Redis/MySQL/ZooKeeper 等分布式存储中。
相关推荐
移幻漂流2 小时前
C/C++内存掌控之道:从内存泄漏到零开销抽象的进阶之路
java·c语言·c++
Java程序员威哥2 小时前
Spring AI快速上手:Java集成ChatGPT/文心一言,30分钟实现智能问答接口
java·人工智能·spring boot·后端·python·spring·云原生
从此不归路2 小时前
Qt5 进阶【1】信号与槽机制深度剖析——从语法到运行时调度
开发语言·qt
w***76552 小时前
PHP vs Go:动态与静态语言的巅峰对决
开发语言·golang·php
HellowAmy2 小时前
我的C++规范 - 请转移到文件
开发语言·c++·代码规范
tqs_123452 小时前
接口的路由和负载均衡
java·python
大闲在人2 小时前
25. 连续盘点系统(Q-R 策略):总成本优化与基于缺货成本的再订货点设定
开发语言·数据分析·供应链管理·智能制造·工业工程
skywalk81632 小时前
介绍一下QuantConnect Lean(python 15k star)
开发语言·python·量化
不凡而大米、2 小时前
报错:传入的请求具有过多的参数。该服务器支持最多2100个参数
java·开发语言·mybatis