synchronized和lock的区别

这是我参与8月更文挑战的第15天,活动详情查看:8月更文挑战

WangScaler: 一个用心创作的作者。

声明:才疏学浅,如有错误,恳请指正。

两者对比

  • synchronized是属于jvm层面的的关键字,底层通过monitorenter、monitorexit指令实现的;而lock是属于一个类。
  • synchronized在代码执行异常时或正常执行完毕后,jvm会自动释放锁;而lock不行使用lock必须加上异常处理,而且必须在finally块中写上unlock()释放锁。
  • synchronized不可中断,只能等待程序执行完毕或者异常退出;而lock可通过interrupt来中断,可参考示例
  • synchronized不能精确唤醒指定的线程;而lock可以通过Condition精确唤醒。可参考示例
  • synchronized无法判断锁的状态,从而无法知道是否获取锁;而lock可以判断锁的状态,可参考示例

中断响应

lock可以通过interrupt中断,而isInterrupted可以判断线程是否被中断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
java复制代码package com.wangscaler.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* @author WangScaler
* @date 2021/8/14 15:41
*/
class Resource {
   private Lock lock = new ReentrantLock();
   private int num = 1;

   protected void getLock() throws InterruptedException {
       lock.lockInterruptibly();
       try {
           System.out.println(Thread.currentThread().getName() + "得到了锁");
           while (!Thread.currentThread().isInterrupted()) {
               System.out.println(Thread.currentThread().getName() + num + "次执行");
               ++num;
               if (num == 10) {
                   System.out.println(Thread.currentThread().getName() + "即将中断");
                   Thread.currentThread().interrupt();
              }
          }
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           lock.unlock();
           System.out.println(Thread.currentThread().getName() + "释放了锁");
      }
  }
}

public class LockDemo {
   public static void main(String[] args) {
       Resource resource = new Resource();
       new Thread(() -> {
           try {
               resource.getLock();
          } catch (InterruptedException e) {
               e.printStackTrace();
          }
      }, "A").start();
  }
}

首先启动线程,如果num小于10则不中断线程,此时循环执行,直到num自增到10,则执行中断Thread.currentThread().interrupt();,此时循环条件变为false,线程结束。而synchronized一旦执行不能中断,要么执行完毕,要么程序异常。

lock精确唤醒示例

lock可以通过Condition精确唤醒。

比如我们有三个线程A、B、C,我们需要保证他们的执行顺序是A-B-C那么我们可以这样写当A线程执行完通过signal();方法来唤醒B,同理一次类推循环唤醒。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
java复制代码package com.wangscaler.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* @author WangScaler
* @date 2021/8/14 15:41
*/
class Resource {
   private int num = 1;
   private Lock lock = new ReentrantLock();
   private Condition condition = lock.newCondition();
   private Condition condition1 = lock.newCondition();
   private Condition condition2 = lock.newCondition();

   protected void startRunA() {
       lock.lock();
       try {
           while (num != 1) {
               condition.await();
          }
           for (int i = 0; i < num; i++) {
               int number = i + 1;
               System.out.println(Thread.currentThread().getName() + number + "次执行");
          }
           ++num;
           condition1.signal();
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           lock.unlock();
      }
  }

   protected void startRunB() {
       lock.lock();
       try {
           while (num != 2) {
               condition1.await();
          }
           for (int i = 0; i < num; i++) {
               int number = i + 1;
               System.out.println(Thread.currentThread().getName() + number + "次执行");
          }
           ++num;
           condition2.signal();
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           lock.unlock();
      }
  }

   protected void startRunC() {
       lock.lock();
       try {
           while (num != 3) {
               condition2.await();
          }
           for (int i = 0; i < num; i++) {
               int number = i + 1;
               System.out.println(Thread.currentThread().getName() + number + "次执行");
          }
           num = 1;
           condition.signal();
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           lock.unlock();
      }
  }
}

public class LockDemo {
   public static void main(String[] args) {
       Resource resource = new Resource();
       new Thread(() -> {
           for (int i = 0; i < 4; i++) {
               resource.startRunA();
          }
      }, "A").start();

       new Thread(() -> {
           for (int i = 0; i < 4; i++) {
               resource.startRunB();
          }
      }, "B").start();
       new Thread(() -> {
           for (int i = 0; i < 4; i++) {
               resource.startRunC();
          }
      }, "C").start();
  }
}

当然这些线程之间就像存在依赖关系一样,只有A能唤醒B,B唤醒C,C唤醒A。就像上述的案例三个线程都是执行4次,可以保证程序的正确执行,但是当B线程改为3次,程序就无法终止,因为C线程一直处于await状态,等待B线程的唤醒,然而B线程已经结束了。不过lock能精确的控制线程的执行顺序,而synchronized则做不到这点,synchronized只能随机唤醒线程。

获取锁的状态

而lock可以通过tryLock判断锁的状态。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
java复制代码package com.wangscaler.lock;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
* @author WangScaler
* @date 2021/8/14 15:41
*/
class Resource {
   private Lock lock = new ReentrantLock();

   protected void testLock() {
       if (lock.tryLock()) {
           System.out.println(Thread.currentThread().getName() + "获取锁成功");
           try {
               Thread.sleep(3000);
          } catch (Exception e) {
               e.printStackTrace();
          } finally {
               System.out.println(Thread.currentThread().getName() + "释放锁");
               lock.unlock();
          }
      } else {
           System.out.println(Thread.currentThread().getName() + "获取锁失败");
      }
  }
}

public class LockDemo {
   public static void main(String[] args) {
       Resource resource = new Resource();
       new Thread(() -> {
           resource.testLock();
      }, "A").start();

       new Thread(() -> {
           resource.testLock();
      }, "B").start();
       new Thread(() -> {
           resource.testLock();
      }, "C").start();
  }
}

tryLtrock方法尝试去获取锁,如果获取成功则返回布尔值true,如果获取失败则返回false。所以可以根据tryLtrock()来判断线程是否获的锁。

总结:

在资源竞争不是很激烈的情况下,可以选择synchronized,反之选择lock。synchronized是由jvm管理的,对程序员的要求较低,而lock则相反,如果操作不当,反而会带来严重的后果。

synchronized已经加入了线程自旋和适应性自旋以及锁消除、锁粗化、偏向锁,慢慢的从重量级锁转换成轻量级锁,优势也越来越明显。总之如何选择,大家根据实际情况进行选择吧。

来都来了,点个赞再走呗!

关注WangScaler,祝你升职、加薪、不提桶!

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%