高级并发编程系列七(锁入门)

1.引子

昨天是1024程序员节日,看到很多朋友发了朋友圈,在这里我想到了问一个问题:1024是2的多少此方?关于这个问题,如果你还需要默算而不是靠潜意识就能回答上来,那说明你不是一个合格的程序员,开个玩笑!

下面进入正题,今天开始在高级并发编程系列中,我们正式进入第二个小节系列: 。说起锁你一定不会陌生。日常生活中,我们经常谈及的很多都与锁有关,你比如说:出门的时候记得把门 好、 保险柜、装个防盗 等等。这些都是我们生活中的锁,锁好门、锁好保险柜都是为了安全 ;它其实与我们程序中锁目的是一致的,在程序中加锁的目的也是为了安全,这又是一个证明世间万物道理想通的铁证。

我们暂且记住,加锁的目的是为了:安全。这个系列中我们准备分享这么几个内容:

  • 锁定义
  • 常见锁分类
  • Lock接口和它的常见实现类分析
  • ReentrantLock使用案例
  • ReentrantReadWriteLock使用案例

今天这一篇,让我们先从入门开始,那么来吧

bash 复制代码
#考考你:
1.你能用自己的话,结合你的理解给锁下一个定义吗
2.你能说出常见的锁分类有哪些吗

2.案例

2.1.第一个加锁案例

我们先看一个大家都熟悉,而可能又陌生的案例操作。在看之前,你需要先思考一下:在大多数编程语言中,都提供了:++自曾的操作,关于这个操作它是线程安全的吗

2.1.1.案例版本一:不加锁

2.1.1.1.任务线程
csharp 复制代码
/**
 * 任务线程
 */
class AddITask implements Runnable{
​
    @Override
    public void run() {
        // for循环,让add_i变量自增操作:10000次
        for (int i = 0; i < 10000; i++){
            NeedDoLockDemo.addI();
        }
    }
​
}
2.1.1.2.主程序
csharp 复制代码
/**
 * 演示需要加锁的基本操作
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2020/10/24 21:35
 */
public class NeedDoLockDemo {
​
    /**
     * 自曾操作变量add_i
     */
    public static int add_i = 0;
​
    /**
     * addI方法,实现add_i变量自曾操作
     * 注意:这里没有加锁
     */
    public  static void addI(){
        add_i ++;
    }
​
    public static void main(String[] args) {
        // 创建任务线程对象
        Runnable r1 = new AddITask();
​
        // 用20个线程,并行执行自增操作
        for(int i = 0; i < 20; i++){
            new Thread(r1).start();
        }
​
        // 循环等待子线程执行结束
        while (Thread.activeCount() > 2){
            ;
        }
​
        // 打印add_i变量值,预期结果:200000
        System.out.println("20个线程并行自增后,结果:" + add_i);
    }
​
}
2.1.1.3.执行结果

结果分析:

  • add_i变量初始值是:0

    arduino 复制代码
     public static int add_i = 0
  • 通过20个线程,并行执行自增操作

    scss 复制代码
    // 用20个线程,并行执行自增操作
    for(int i = 0; i < 20; i++){
        new Thread(r1).start();
    }
  • 每个线程,自增10000次

    ini 复制代码
    // for循环,让add_i变量自增操作:10000次
    for (int i = 0; i < 10000; i++){
         NeedDoLockDemo.addI();
    }
  • 预期结果:20 * 10000 = 200000

  • 实际结果:198359

  • 执行结果与预期结果不符,发生了并发不安全问题

2.1.2.案例版本二:加锁

在案例版本一中,由于没有加锁,发生了并发不安全的问题。这里有一个小知识点:对于jvm来说,++操作不是一个线程安全的操作,因为自增++操作,它不是一个原子性的操作

那如何实现自增++的放心操作,人畜无害呢?答案是:加锁。我们再来看加锁的版本。

2.1.2.1.方法addI加锁
arduino 复制代码
/**
* addI方法,实现add_i变量自曾操作
* 注意:这里的synchronized关键字
*/
public synchronized static void addI(){
      add_i ++;
}
2.1.2.2.执行结果

结果分析:

  • 加锁后,add_i变量的最终执行结果是:200000,符合预期

2.2.锁定义及常见锁分类

2.2.1.锁定义

我们知道了,在多线程并发操作下,需要考虑线程安全的问题。这是因为有共享资源的存在,所谓共享资源即是每个线程都会去操作的资源,你需要,我也需要,可以这么去理解 ,比如说上面案例中的add_i变量

那么问题来了,在并发操作的情况下,是不管先来后到的,即便后来,但是可能却先把活干了。这样下去,世界就乱了,没有规则没有秩序,重新陷入混沌状态

所以需要一种规则,建立一套秩序,让程序世界不能乱!锁就是编程世界中,解决混乱的规则和秩序,它的本质是在整体并行执行的应用程序中,实现局部串行有序执行

到了这里,我们可以用一句简洁的话来描述锁:锁是一种工具,用于控制对共享资源访问的工具

2.2.2.锁分类

关于锁的分类,如果要细分会非常多,非常难记,且没有必要。我们关注几类常见的吧:

  • 乐观锁与悲观锁

    • 乐观锁

      bash 复制代码
      #1.所谓乐观锁,它是典型的乐天派,认为在操作共享资源的时候,永远都只有自己一个人(一个线程)在操作,不会有其它线程与自己争
      #2.因此乐观锁本质上不加锁,而是引入了冲突检测的机制。关于冲突检测机制,在后面分享CAS的时候,我们在详细讨论
      #3.乐观锁适用于:读多写少,冲突小的业务场景
    • 悲观锁

    bash 复制代码
    #1.所谓悲观锁,它是典型的悲观派,时时刻刻都认为:在操作共享资源的时候,永远都有别人(别的线程),在跟自己抢!
    #2.因此不管三七二十一,先锁了再说,让你抢不着
    #悲观锁适用于:写多读少,容易发生冲突的业务场景
  • 可重入锁与非可重入锁

    • 可重入锁

      bash 复制代码
      #1.所谓可重入锁,一个线程拿到该锁以后,在释放以前,可以重复多次获取到该锁
      #2.在juc中提供的ReentrantLock即是可重入锁
    • 非可重入锁

      bash 复制代码
      #1.所谓非可重入锁,一个线程拿到该锁以后,在释放以前,不能重复多次获取到该锁
      #2.在jdk中提供的synchronized锁,即是非可重入锁
  • 共享锁与排它锁

    • 共享锁

      bash 复制代码
      #1.所谓共享锁,表示该锁比较大方,独乐乐不如众乐乐,一个锁可以被多个线程同时获取拥有
      #2.在juc中提供的ReentrantReadWriteLock锁中的读锁,即是共享锁
    • 排它锁

      bash 复制代码
      #1.所谓排它锁,表示有你没我,有我没你。即一个锁被一个线程获取以后,便不能再被其他线程获取
      #2.我们平常见的较多的锁,都是排它锁。比如ReentrantLock、synchronized、ReentrantReadWriteLock中的写锁等
相关推荐
盼海6 分钟前
排序算法(六)--堆排序
java·算法·排序算法
陈序缘12 分钟前
Rust 力扣 - 198. 打家劫舍
开发语言·后端·算法·leetcode·rust
凭君语未可14 分钟前
豆包MarsCode算法题:三数之和问题
java·算法
kirito学长-Java20 分钟前
springboot/ssm考试系统Java学生在线考试系统web学习论坛源码
java·spring boot·学习
帅气的花泽类23 分钟前
error Unexpected ‘debugger‘ statement no-debugger
java·maven
yyycqupt23 分钟前
数据库连接池(二)
linux·数据库·c++·后端·单例模式
cooldream200938 分钟前
SpringMVC 执行流程详解
java·spring·springmvc
redemption_240 分钟前
SpringMVC-01-回顾MVC
java
techdashen42 分钟前
Go context.Context
开发语言·后端·golang
凡人的AI工具箱44 分钟前
40分钟学 Go 语言高并发:Select多路复用
开发语言·后端·架构·golang