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

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

  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. tomcat 日志

    1.Tomcat的日志(./tomca/logs/) 分为5类,这里面 1和5比较重要 .catalina.--.log 或者 catalina.out: 引擎的日志文件 .host-manager. ...

  2. Mysql 删除从数据库的relay logs最佳方式、最安全方式

    情景 MySQL数据库主从复制在默认情况下从库的relay logs会在SQL线程执行完毕后被自动删除.但是:在relay_log_purge = 0和MHA集群下,不会被自动删除,需要手动删除.如何 ...

  3. 使用Unicode(宽字节字符集);多字节字符集中定义宽字节变量

    2012-03-25 14:54 (分类:计算机程序) 2.2 宽字符和C 宽字符不一定是Unicode.Unicode是宽字符集的一种.然而,因为本书的焦点是Windows而不是C执行的理论,所以书 ...

  4. 无线网络WPA加密算法基础

    2013-11-13 23:08 (分类:网络安全) 对无线没什么认识,总听说有人蹭网,还有卖蹭网器的,于是补充一下知识. 无线加密有两类:WEP WAP,目前采用WEP加密的非常少了,WEP应该只是 ...

  5. Docker Compose搭建Redis一主二从三哨兵高可用集群

    一.Docker Compose介绍 https://docs.docker.com/compose/ Docker官方的网站是这样介绍Docker Compose的: Compose是用于定义和运行 ...

  6. Javase-坦克大战小游戏,为什么会出现上方向和左方向的子弹不能发射的情况?检查了好久,有大佬帮帮忙吗,小白睡不着

    //为什么会出现上方向和左方向的子弹不能发射的情况?检查了好久,有大佬帮帮忙吗,小白睡不着 package TanKe.lbl;import java.awt.*;import java.awt.ev ...

  7. Go1.14发布了,快来围观新的特性啦

    如期而至,Go1.14发布了,和往常一样,该版本保留了Go 1兼容性的承若,这个版本的大部分更新在工具链 .运行时库的性能提升方面,总的来说,还是在已有的基础上不断优化提成,大家期待的泛型还没有到来, ...

  8. Spring Boot 2从入门到放弃(持续更新)

    入门 Spring Boot 2项目的搭建和启动(入门篇1) Spring Boot 2项目的搭建和启动(入门篇2) spring boot 2项目自定义父pom Spring Boot 2开发工具s ...

  9. opencv —— moments 矩的计算(空间矩/几何矩、中心距、归一化中心距、Hu矩)

    计算矩的目的 从一幅图像计算出来的矩集,不仅可以描述图像形状的全局特征,而且可以提供大量关于该图像不同的几何特征信息,如大小,位置.方向和形状等.这种描述能力广泛应用于各种图像处理.计算机视觉和机器人 ...

  10. Android5.1 WebView遇坑笔记-Resources$NotFoundException

    Bugly遇到异常 查找原因,分析发现崩溃发生在Android版本21和22上,在网上查找资料发现下面解决方案 使用自定义WebView替换原生自带WebView解决 package com.test ...