大家都知道加锁是用来在并发情况防止同一个资源被多方抢占的有效手段,加锁其实就是同步互斥(或称独占)也行,即:同一时间不论有多少并发请求,只有一个能处理,其余要么排队等待,要么放弃执行。关于锁的实现网上大把的例子,我这里只是梳理与总结一下,以便参考方便。

同步互斥按作用范围可分为:

  1. 线程间同步互斥

    下面分别通过代码示例来演示常见的线程间同步互斥的实现方法:

    1. synchronized


      /**
      * 线程间同步(synchronized同步互斥锁,开启多个线程,若某个线程获得锁,则其余线程全部阻塞等待排队,当释放锁后,则下一个继续获得锁,其余线程仍等待)
      */
      private void testSynchronized() {
      ExecutorService executorService = Executors.newFixedThreadPool(2);
      Runnable runnable = () -> {
      long threadId = Thread.currentThread().getId();
      for (int i = 0; i <= 100; i++) {
      synchronized (lockObj) {
      if (count > 0) {
      try {
      Thread.sleep(200L);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.printf("threadId:%s,number:%d --count:%d %n", threadId, i, --count);
      }
      } //若未加锁,则可能会出现负数,即并发问题
      // if (count>0){
      // try {
      // Thread.sleep(200L);
      // } catch (InterruptedException e) {
      // e.printStackTrace();
      // }
      // System.out.printf("threadId:%s,number:%d --count:%d %n",threadId,i,--count);
      // }
      }
      }; executorService.execute(runnable);
      executorService.execute(runnable); System.out.printf("lasted count:%d", count);
      }
    2. synchronized同步互斥锁+通知等待模式

      /**
      * 线程间同步(synchronized同步互斥锁+通知等待模式,开启多个线程,当获得锁后,则可通过Object.notify()方法发出通知,通知其它等待锁或wait情况下恢复继续执行,示例演示的是生产与消费互相等待)
      */
      private void testWaitAndNotify() {
      count = 0;
      ExecutorService executorService = Executors.newFixedThreadPool(2);
      Runnable productRunnable = () -> {
      long threadId = Thread.currentThread().getId();
      for (int i = 0; i <= 50; i++) {
      synchronized (lockObj) { //获取锁
      try {
      Thread.sleep(200L);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.printf("threadId:%s,number:%d --生产后 count:%d %n", threadId, i, ++count);
      lockObj.notify();//发出通知
      try {
      System.out.printf("threadId:%s,number:%d,等待生产%n", threadId, i);
      if (i == 50) break;
      lockObj.wait();//等待通知,阻塞当前线程
      System.out.printf("threadId:%s,number:%d,收到通知,准备生产%n", threadId, i);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      }
      count = -1;
      System.out.printf("threadId:%s,已生产完了。%n", threadId);
      }; Runnable consumeRunnable = () -> {
      long threadId = Thread.currentThread().getId();
      for (int i = 0; i <= 200; i++) {
      synchronized (lockObj) { //获取锁
      if (count > 0) {
      try {
      Thread.sleep(200L);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.printf("threadId:%s,number:%d --消费后 count:%d %n", threadId, i, --count);
      lockObj.notify(); //发出通知
      } else {
      try {
      System.out.printf("threadId:%s,number:%d,等待消费%n", threadId, i);
      if (count == -1) break;
      lockObj.wait();//等待通知,阻塞当前线程
      System.out.printf("threadId:%s,number:%d,收到通知,准备消费%n", threadId, i);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      }
      }
      System.out.printf("threadId:%s,已消费完了。%n", threadId); }; executorService.execute(consumeRunnable);
      executorService.execute(productRunnable); }
    3. 条件锁ReentrantLock、Condition


      /**
      * 线程间同步(条件锁ReentrantLock、Condition,开启多个线程,当lock()获取锁后,则可以通过Lock的条件实例方法signal发送信号,通知其它等待锁或await情况下恢复继续执行,示例演示的是生产与消费互相等待)
      */
      private void testLock() {
      final Lock lock = new ReentrantLock();
      final Condition lockCond = lock.newCondition(); count = 0;
      ExecutorService executorService = Executors.newFixedThreadPool(2);
      Runnable productRunnable = () -> {
      long threadId = Thread.currentThread().getId();
      lock.lock();//先获得锁
      for (int i = 0; i <= 50; i++) {
      try {
      Thread.sleep(200L);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.printf("threadId:%s,number:%d --生产后 count:%d %n", threadId, i, ++count);
      lockCond.signal();//放出信号
      try {
      System.out.printf("threadId:%s,number:%d,等待生产%n", threadId, i);
      if (i == 50) break;
      lockCond.await();//等待信号,阻塞当前线程
      System.out.printf("threadId:%s,number:%d,收到通知,准备生产%n", threadId, i);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }
      lock.unlock();//释放锁
      count = -1;
      System.out.printf("threadId:%s,已生产完了。%n", threadId);
      }; Runnable consumeRunnable = () -> {
      long threadId = Thread.currentThread().getId();
      lock.lock();//先获得锁
      for (int i = 0; i <= 200; i++) {
      if (count > 0) {
      try {
      Thread.sleep(200L);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.printf("threadId:%s,number:%d --消费后 count:%d %n", threadId, i, --count);
      lockCond.signal();//放出信号
      } else {
      try {
      System.out.printf("threadId:%s,number:%d,等待消费%n", threadId, i);
      if (count == -1) break;
      lockCond.await();//等待信号,阻塞当前线程
      System.out.printf("threadId:%s,number:%d,收到通知,准备消费%n", threadId, i);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      } }
      lock.unlock();
      System.out.printf("threadId:%s,已消费完了。%n", threadId); }; executorService.execute(consumeRunnable);
      executorService.execute(productRunnable);
      }
    4. Future


      /**
      * 线程间同步(Future,采用Executors.submit开启1个或多个线程返回Future,线程后台异步执行不阻塞主线程,当需要获得线程结果时,即:Future.get,则会等待获取结果,
      * 当然可以使用CompletableFuture来实现完全的异步回调处理结果,无需任何阻塞)
      */
      private void testFuture() {
      //refer:https://www.cnblogs.com/xiaoxi/p/8303574.html
      ExecutorService executorService = Executors.newSingleThreadExecutor();
      Future<Long> task = executorService.submit(() -> {
      long total = 0;
      System.out.println("子线程for loop start...");
      for (int i = 0; i <= 100; i++) {
      try {
      Thread.sleep(50L);
      } catch (InterruptedException e) {
      e.printStackTrace();
      } total += i;
      }
      System.out.printf("子线程for loop end,total=%d %n", total);
      return total;
      }); //主线程处理其它逻辑,此时是与子线程在并行执行
      for (int n = 0; n <= 30; n++) {
      System.out.printf("主线程for loop中,n=%d %n", n);
      } try {
      long result = task.get();//等待子线程结果,如果未执行完则会阻塞主线程直到子线程完成出结果
      System.out.printf("主线程获取子线程计算的结果,total=%d %n", result);
      } catch (Exception e) {
      e.printStackTrace();
      } //使用CompletableFuture可异步回调获取结果,不会阻塞主线程
      CompletableFuture.supplyAsync(() -> {
      long total = 0;
      System.out.println("子线程for loop start...");
      for (int i = 0; i <= 100; i++) {
      try {
      Thread.sleep(50L);
      } catch (InterruptedException e) {
      e.printStackTrace();
      } total += i;
      }
      System.out.printf("threadId:%s,子线程for loop end,total=%d %n", Thread.currentThread().getId(), total);
      return total;
      }).thenAccept(result -> {
      //当子线程执行完成后会回调该方法
      System.out.printf("threadId:%s,回调获取子线程计算的结果,total=%d %n", Thread.currentThread().getId(), result);
      }); //主线程处理其它逻辑,此时是与子线程在并行执行
      long threadId = Thread.currentThread().getId();
      for (int n = 0; n <= 30; n++) {
      System.out.printf("threadId:%s,主线程for loop2中,n=%d %n", threadId, n);
      } System.out.printf("threadId:%s,主线程已执行完成。%n", threadId); }
    5. CountDownLatch(CyclicBarrier类似,支持分批并发执行,分批次阶段等待,且支持重设计数器)


      /**
      * 线程间同步(CountDownLatch,同时运行多个线程,在CountDownLatch计数器count为0前主线程会阻塞等待)
      */
      private void testCountDownLatch() {
      ExecutorService executorService = Executors.newFixedThreadPool(3);
      final CountDownLatch latch = new CountDownLatch(3);
      Runnable runnable = () -> {
      long threadId = Thread.currentThread().getId();
      for (int i = 0; i <= 100; i++) {
      try {
      Thread.sleep(50L);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      System.out.printf("threadId:%s,number:%d %n", threadId, i);
      }
      System.out.printf("threadId:%s,已处理完成。%n", threadId);
      latch.countDown();//扣减计数器-1
      }; //开3个线程并行处理
      for (int i = 1; i <= 3; i++) {
      executorService.execute(runnable);
      } long mainThreadId = Thread.currentThread().getId();
      try {
      System.out.printf("threadId:%s,主线程等待中...%n", mainThreadId);
      latch.await();//等待全部执行完成,即计数器为0,阻塞主线程
      } catch (InterruptedException e) {
      e.printStackTrace();
      } System.out.printf("threadId:%s,主线程确认所有子线程都处理完成,count:%d,开始执行主线程逻辑。%n", mainThreadId, latch.getCount()); System.out.printf("threadId:%s,主线程已执行完成!%n", mainThreadId); }
    6. Semaphore


      /**
      * 线程间同步(Semaphore,开启多个线程,使用acquire获取1个许可【可指定一次获取多个许可】,
      * 若未能获取到则等待,若已获得许可则占用了1个可用许可总数且可进入继续执行,待执行完成后应释放许可)
      */
      private void testSemaphore(){
      Semaphore wcSemaphore = new Semaphore(5,true);
      Runnable runnable =() -> {
      long threadId = Thread.currentThread().getId();
      System.out.printf("threadId:%s,等待进入WC,目前还有:%d空位,排队等候人数:%d %n", threadId,wcSemaphore.availablePermits(), wcSemaphore.getQueueLength());
      try {
      wcSemaphore.acquire();
      System.out.printf("threadId:%s,进入WC,目前还有:%d空位,排队等候人数:%d,关门 %n", threadId,wcSemaphore.availablePermits(), wcSemaphore.getQueueLength());
      Thread.sleep(1000L);
      System.out.printf("threadId:%s,离开WC,目前还有:%d空位,排队等候人数:%d,开门 %n", threadId,wcSemaphore.availablePermits(), wcSemaphore.getQueueLength());
      wcSemaphore.release();
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      }; ExecutorService executorService = Executors.newFixedThreadPool(5);
      for (int n=1;n<=10;n++){
      executorService.execute(runnable);
      } long mainThreadId = Thread.currentThread().getId();
      System.out.printf("threadId:%s,清洁阿姨等待打扫WC,目前还有:%d空位,排队等候人数:%d %n",
      mainThreadId,wcSemaphore.availablePermits(),wcSemaphore.getQueueLength());
      //如果还有排队且剩余空位未全部处理则等待
      while (wcSemaphore.hasQueuedThreads() && wcSemaphore.drainPermits()!=5 && wcSemaphore.availablePermits()!=5){
      try {
      Thread.sleep(50L);
      } catch (InterruptedException e) {
      e.printStackTrace();
      }
      } try {
      wcSemaphore.acquire(5);
      System.out.printf("threadId:%s,清洁阿姨开始打扫WC,关上WC入口,即所有人均不可再使用,目前还有:%d空位,排队等候人数:%d %n",
      mainThreadId,wcSemaphore.availablePermits(), wcSemaphore.getQueueLength());
      } catch (InterruptedException e) {
      e.printStackTrace();
      } }
  2. 进程间同步互斥

    1. 采用FileLock实现进程间同步互斥,如果是C#、C++则可以使用Mutex


    /**
    * 进程间同步(FileLock文件锁,同时开启多个进程实例,若已获得锁的实例在执行,则后面的进程实例均只能等待,当然可以使用tryLock非阻塞模式)
    */
    private void testFileLock() {
    File lockFile = new File(System.getProperty("user.dir") + File.separator + "app.lock");
    if (!lockFile.exists()) {
    try {
    if (!lockFile.createNewFile()) {
    System.out.printf("创建文件失败:" + lockFile.getAbsolutePath());
    return;
    }
    } catch (IOException e) {
    e.printStackTrace();
    }
    } try { FileChannel fileChannel = new FileOutputStream(lockFile).getChannel();
    String jvmName = ManagementFactory.getRuntimeMXBean().getName(); System.out.printf("jvm ProcessName:%s, 准备获取锁 ... %n", jvmName); FileLock lock = fileChannel.lock();//获取文件锁 for (int i = 0; i <= 100; i++) {
    try {
    Thread.sleep(100L);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    System.out.printf("jvm ProcessName:%s, number:%d %n", jvmName, i);
    } lock.release();
    fileChannel.close(); System.out.printf("jvm ProcessName:%s, 处理完成,释放锁 %n", jvmName); } catch (Exception e) {
    e.printStackTrace();
    } }
  3. 机器间同步互斥(即分布式锁)

    1. 基于DB实现(利用DB的更新独占锁)

      ---网上也有很多,我这里把之前用C#实现的基于DB的分布式锁的例子(JAVA同理)拿出来【原理是:有一张T_Locking表,SType=LOCK名,SValue=LOCK唯一标识值,RecordTime=加锁记录时间,获取锁时直接根据SType=N'Lock名' and SValue=N''来更新,若被其它实例更新,则SValue不可能为空,则获取锁失败,然后进一步判断是否出现过期锁的情况,如果过期则仍可以尝试更新获取锁即可】

              /// <summary>
      /// 设置分布式锁
      /// </summary>
      /// <returns></returns>
      private bool SetDistributedLockBasedDB()
      {
      bool hasLock = false;
      try
      {
      var sqlDapperUtil = new SqlDapperUtil(Constants.CfgKey_KYLDConnectionName);
      ////此处利用DB的更新排它锁,确保并发时只有一个优先执行,而当某个执行成功后,其它后续执行会因为条件更新不满足而更新失败,实现了多并发时只有一个能更新成功,即获得锁。
      hasLock = sqlDapperUtil.ExecuteCommand("update [dbo].[T_Locking] set SValue=@SValue,RecordTime=getdate() where SType=N'Lock名' and SValue=N'' ", new { SValue = Lock唯一标识值 }); if (!hasLock) //如果未获得锁,还需要考虑加锁后未被正常释放锁的情况,故如下再次尝试对加锁超过1小时以上的进行重新更新再次获得锁,避免无效锁一直处于锁定状态
      {
      hasLock = sqlDapperUtil.ExecuteCommand("update [dbo].[T_Locking] set SValue=@SValue,RecordTime=getdate() " +
      "where SType = N'Lock名' and SValue <> N'' and RecordTime < DATEADD(hh, -1, getdate())",
      new { SValue = Lock唯一标识值 });
      }
      }
      catch (Exception ex)
      {
      logger.Error("SetDistributedLockBasedDB Error: " + ex.ToString());
      } return hasLock;
      } /// <summary>
      /// 释放分布式锁
      /// </summary>
      private void ReleaseDistributedLockBasedDB()
      {
      try
      {
      var sqlDapperUtil = new SqlDapperUtil(Constants.CfgKey_KYLDConnectionName);
      sqlDapperUtil.ExecuteCommand("update [dbo].[T_Locking] set SValue=N'',RecordTime=getdate() where SType=N'Lock名' and SValue=@SValue", new { SValue = Lock唯一标识值 });
      }
      catch (Exception ex)
      {
      logger.Error("ReleaseDistributedLockBasedDB Error: " + ex.ToString());
      }
      }
    2. 基于Redis实现

      实现方式1(原生实现):Redis分布式锁的正确实现方式(Java版)

      实现方式2(Redlock):Redis分布式锁的官方算法RedLock以及Java版本实现库Redisson

    3. 基于Zookeeper实现

      死磕 java同步系列之zookeeper分布式锁

​ *有同时列出三种实现方案的文章,可参见: Java分布式锁三种实现方案

JAVA并发同步互斥实现方式总结的更多相关文章

  1. Java并发-同步容器篇

    作者:汤圆 个人博客:javalover.cc 前言 官人们好啊,我是汤圆,今天给大家带来的是<Java并发-同步容器篇>,希望有所帮助,谢谢 文章如果有问题,欢迎大家批评指正,在此谢过啦 ...

  2. Java并发—同步容器和并发容器

    简述同步容器与并发容器 在Java并发编程中,经常听到同步容器.并发容器之说,那什么是同步容器与并发容器呢?同步容器可以简单地理解为通过synchronized来实现同步的容器,比如Vector.Ha ...

  3. Java并发--同步容器

    为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch).今天我们就来讨论下同步容器. ...

  4. Java并发——同步工具类

    CountDownLatch  同步倒数计数器 CountDownLatch是一个同步倒数计数器.CountDownLatch允许一个或多个线程等待其他线程完成操作. CountDownLatch对象 ...

  5. Java并发——同步容器与并发容器

    同步容器类 早期版本的JDK提供的同步容器类为Vector和Hashtable,JDK1.2 提供了Collections.synchronizedXxx等工程方法,将普通的容器继续包装.对每个共有方 ...

  6. java并发-同步容器类

    java平台类库包含了丰富的并发基础构建模块,如线程安全的容器类以及各种用于协调多个相互协作的线程控制流的同步工具类. 同步容器类 同步容器类包括Vector和Hashtable,是早期JDK的一部分 ...

  7. Java 并发同步工具(转)

    转自:https://www.jianshu.com/p/e80043ac4115 在 java 1.5 中,提供了一些非常有用的辅助类来帮助我们进行并发编程,比如 CountDownLatch,Cy ...

  8. Java 并发同步器之CountDownLatch、CyclicBarrier

    一.简介 1.CountDownLatch是一个同步计数器,构造时传入int参数,该参数就是计数器的初始值,每调用一次countDown()方法,计数器减1,计数器大于0 时,await()方法会阻塞 ...

  9. Java并发编程基础之volatile

    首先简单介绍一下volatile的应用,volatile作为Java多线程中轻量级的同步措施,保证了多线程环境中“共享变量”的可见性.这里的可见性简单而言可以理解为当一个线程修改了一个共享变量的时候, ...

随机推荐

  1. ElasticSearch集群-Windows

    概述 ES集群是一个P2类型的分布式系统,除了集群状态管理以外,其他所有的请求都可以发送到集群内任意一台节点上,这个节点可以自己找到需要转发给哪些节点,并且直接跟这些节点通信.所以,从网络架构及服务配 ...

  2. 将 ASP.NET Core 2.1 升级到最新的长期支持版本ASP.NET Core 3.1

    目录 前言 Microsoft.AspNetCore.Mvc.ViewFeatures.Internal 消失了 升级到 ASP.NET Core 3.1 项目文件(.csproj) Program. ...

  3. leetcode--js--Two Sum

    问题描述: 给定一个整数数列,找出其中和为特定值的那两个数. 你可以假设每个输入都只会有一种答案,同样的元素不能被重用. 示例: 给定 nums = [2, 7, 11, 15], target = ...

  4. 基于Jupyter Notebooks的C# .NET Interactive安装与使用

    .NET Interactive发布预览版了,可以像Python那样用jupyter notebooks来编辑C#代码.具体可以在GitHub上查看dotnet/interactive项目. 安装步骤 ...

  5. Play! 1.x 访问远程web

    本文参考 Play Framework 控制层发起HTTP请求 (Send Http Request In Controller) 参考连接地址:http://blog.csdn.net/fhzait ...

  6. 使用Teigha.net读取CAD的常用功能模块

    Teigha中实体旋转 代码: using (var trans = database.TransactionManager.StartTransaction()) { Entity ent = tr ...

  7. yarn-site.xml 配置介绍

    yarn-site.xml 配置介绍 yarn.scheduler.minimum-allocation-mb yarn.scheduler.maximum-allocation-mb 说明:单个容器 ...

  8. MySQL数据库的两种连接方式:TCP/IP和Socket

    Linux平台环境下主要有两种连接方式,一种是TCP/IP连接方式,另一种就是socket连接. 在Windows平台下,有name pipe和share memory(不考虑)两种. TCP/IP连 ...

  9. Jupyter Notebook 常用快捷键 (转)

    Jupyter Notebook 有两种键盘输入模式. 编辑模式,允许你往单元中键入代码或文本:这时的单元框线是绿色的. 命令模式,键盘输入运行程序命令:这时的单元框线为蓝色. 命令模式 (按键 Es ...

  10. Warning: curl_setopt() [function.curl-setopt]: CURLOPT_FOLLOWLOCATION cannot be activated when in safe_mode or an open_basedir is set…

    php打印小票错误提示:Warning: curl_setopt() [function.curl-setopt]: CURLOPT_FOLLOWLOCATION cannot be activate ...