互斥同步和线程之间的协作

互斥同步

Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLock。

synchronized

1. 同步一个代码块

  1. public void func() {
  2. synchronized (this) {
  3. // ...
  4. }
  5. }

它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。

对于以下代码,使用 ExecutorService 执行了两个线程,由于调用的是同一个对象的同步代码块,因此这两个线程会进行同步,当一个线程进入同步语句块时,另一个线程就必须等待。

  1. public class SynchronizedExample {
  2.  
  3. public void func1() {
  4. synchronized (this) {
  5. for (int i = 0; i < 10; i++) {
  6. System.out.print(i + " ");
  7. }
  8. }
  9. }
  10. }
  11. public static void main(String[] args) {
  12. SynchronizedExample e1 = new SynchronizedExample();
  13. ExecutorService executorService = Executors.newCachedThreadPool();
  14. executorService.execute(() -> e1.func1());
  15. executorService.execute(() -> e1.func1());
  16. }

  1. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

对于以下代码,两个线程调用了不同对象的同步代码块,因此这两个线程就不需要同步。从输出结果可以看出,两个线程交叉执行。

  1. public static void main(String[] args) {
  2. SynchronizedExample e1 = new SynchronizedExample();
  3. SynchronizedExample e2 = new SynchronizedExample();
  4. ExecutorService executorService = Executors.newCachedThreadPool();
  5. executorService.execute(() -> e1.func1());
  6. executorService.execute(() -> e2.func1());
  7. }

  1. 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9

2. 同步一个方法

  1. public synchronized void func () {
  2. // ...
  3. }

它和同步代码块一样,作用于同一个对象。

3. 同步一个类

  1. public void func() {
  2. synchronized (SynchronizedExample.class) {
  3. // ...
  4. }
  5. }

作用于整个类,也就是说两个线程调用同一个类的不同对象上的这种同步语句,也会进行同步。

  1. public class SynchronizedExample {
  2.  
  3. public void func2() {
  4. synchronized (SynchronizedExample.class) {
  5. for (int i = 0; i < 10; i++) {
  6. System.out.print(i + " ");
  7. }
  8. }
  9. }
  10. }
  11. public static void main(String[] args) {
  12. SynchronizedExample e1 = new SynchronizedExample();
  13. SynchronizedExample e2 = new SynchronizedExample();
  14. ExecutorService executorService = Executors.newCachedThreadPool();
  15. executorService.execute(() -> e1.func2());
  16. executorService.execute(() -> e2.func2());
  17. }

  1. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

4. 同步一个静态方法

  1. public synchronized static void fun() {
  2. // ...
  3. }

作用于整个类。

ReentrantLock

ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁。

  1. public class LockExample {
  2.  
  3. private Lock lock = new ReentrantLock();
  4.  
  5. public void func() {
  6. lock.lock();
  7. try {
  8. for (int i = 0; i < 10; i++) {
  9. System.out.print(i + " ");
  10. }
  11. } finally {
  12. lock.unlock(); // 确保释放锁,从而避免发生死锁。
  13. }
  14. }
  15. }
  16. public static void main(String[] args) {
  17. LockExample lockExample = new LockExample();
  18. ExecutorService executorService = Executors.newCachedThreadPool();
  19. executorService.execute(() -> lockExample.func());
  20. executorService.execute(() -> lockExample.func());
  21. }

  1. 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9

比较

1. 锁的实现

synchronized 是关键字,由 JVM 实现的,而 ReentrantLock 是类,由 JDK 实现的。

2. 性能

新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。

3. 等待可中断

ReentrantLock 可以获取锁的等待时间并可以进行设置,这样避免了死锁。

4. 公平锁

公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。

synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。

5. 锁绑定多个条件

一个 ReentrantLock 可以同时绑定多个 Condition 对象,灵活地实现多路通知。

6. 机制

synchronized 操作Mark World,ReentrantLock 调用Unsafe类的park()方法

使用选择

除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。

线程之间的协作

当多个线程可以一起工作去解决某个问题时,如果某些部分必须在其它部分之前完成,那么就需要对线程进行协调。

join()

在线程中调用另一个线程的 join() 方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。

对于以下代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。

  1. public class JoinExample {
  2.  
  3. private class A extends Thread {
  4. @Override
  5. public void run() {
  6. System.out.println("A");
  7. }
  8. }
  9.  
  10. private class B extends Thread {
  11.  
  12. private A a;
  13.  
  14. B(A a) {
  15. this.a = a;
  16. }
  17.  
  18. @Override
  19. public void run() {
  20. try {
  21. a.join();
  22. } catch (InterruptedException e) {
  23. e.printStackTrace();
  24. }
  25. System.out.println("B");
  26. }
  27. }
  28.  
  29. public void test() {
  30. A a = new A();
  31. B b = new B(a);
  32. b.start();
  33. a.start();
  34. }
  35. }
  36. public static void main(String[] args) {
  37. JoinExample example = new JoinExample();
  38. example.test();
  39. }

  1. A
  2. B

wait() notify() notifyAll()

调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。

它们都属于 Object 的一部分,而不属于 Thread。

只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateException。

使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。

  1. public class WaitNotifyExample {
  2.  
  3. public synchronized void before() {
  4. System.out.println("before");
  5. notifyAll();
  6. }
  7.  
  8. public synchronized void after() {
  9. try {
  10. wait();
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. System.out.println("after");
  15. }
  16. }
  17. public static void main(String[] args) {
  18. ExecutorService executorService = Executors.newCachedThreadPool();
  19. WaitNotifyExample example = new WaitNotifyExample();
  20. executorService.execute(() -> example.after());
  21. executorService.execute(() -> example.before());
  22. }

  1. before
  2. after

wait() 和 sleep() 的区别

  • wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
  • wait() 会释放锁,sleep() 不会。

await() signal() signalAll()

java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。

相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。

使用 Lock 来获取一个 Condition 对象。

  1. public class AwaitSignalExample {
  2.  
  3. private Lock lock = new ReentrantLock();
  4. private Condition condition = lock.newCondition();
  5.  
  6. public void before() {
  7. lock.lock();
  8. try {
  9. System.out.println("before");
  10. condition.signalAll();
  11. } finally {
  12. lock.unlock();
  13. }
  14. }
  15.  
  16. public void after() {
  17. lock.lock();
  18. try {
  19. condition.await();
  20. System.out.println("after");
  21. } catch (InterruptedException e) {
  22. e.printStackTrace();
  23. } finally {
  24. lock.unlock();
  25. }
  26. }
  27. }
  28. public static void main(String[] args) {
  29. ExecutorService executorService = Executors.newCachedThreadPool();
  30. AwaitSignalExample example = new AwaitSignalExample();
  31. executorService.execute(() -> example.after());
  32. executorService.execute(() -> example.before());
  33. }

  1. before
  2. after

免费Java高级资料需要自己领取,涵盖了Java、Redis、MongoDB、MySQL、Zookeeper、Spring Cloud、Dubbo高并发分布式等教程,一共30G。
传送门:https://mp.weixin.qq.com/s/JzddfH-7yNudmkjT0IRL8Q

Java并发编程,互斥同步和线程之间的协作的更多相关文章

  1. Java并发编程:同步容器

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

  2. 【转】Java并发编程:同步容器

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

  3. Java并发编程、多线程、线程池…

    <实战java高并发程序设计>源码整理https://github.com/petercao/concurrent-programming/blob/master/README.md Ja ...

  4. 8、Java并发编程:同步容器

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

  5. 原创】Java并发编程系列2:线程概念与基础操作

    [原创]Java并发编程系列2:线程概念与基础操作 伟大的理想只有经过忘我的斗争和牺牲才能胜利实现. 本篇为[Dali王的技术博客]Java并发编程系列第二篇,讲讲有关线程的那些事儿.主要内容是如下这 ...

  6. Java并发编程:如何创建线程?

    Java并发编程:如何创建线程? 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程去执行一个子任务.下面先讲述一下Java中的应用程序和进程相关的概念知识, ...

  7. Java 并发编程——Executor框架和线程池原理

    Eexecutor作为灵活且强大的异步执行框架,其支持多种不同类型的任务执行策略,提供了一种标准的方法将任务的提交过程和执行过程解耦开发,基于生产者-消费者模式,其提交任务的线程相当于生产者,执行任务 ...

  8. 【转】Java并发编程:如何创建线程?

    一.Java中关于应用程序和进程相关的概念 在Java中,一个应用程序对应着一个JVM实例(也有地方称为JVM进程),一般来说名字默认是java.exe或者javaw.exe(windows下可以通过 ...

  9. [Java并发编程(二)] 线程池 FixedThreadPool、CachedThreadPool、ForkJoinPool?为后台任务选择合适的 Java executors

    [Java并发编程(二)] 线程池 FixedThreadPool.CachedThreadPool.ForkJoinPool?为后台任务选择合适的 Java executors ... 摘要 Jav ...

随机推荐

  1. 九度OJ 1121:首字母大写 (字符串处理)

    时间限制:1 秒 内存限制:32 兆 特殊判题:否 提交:2865 解决:1007 题目描述: 对一个字符串中的所有单词,如果单词的首字母不是大写字母,则把单词的首字母变成大写字母. 在字符串中,单词 ...

  2. yuicompressor

      yui/yuicompressor: YUI Compressor https://github.com/yui/yuicompressor    YUI Compressor 详细介绍 YUI ...

  3. 负载均衡实现,一个域名对应多个IP地址

    负载均衡实现,一个域名对应多个IP地址 - 宏宇 - 博客园 https://www.cnblogs.com/cuihongyu3503319/archive/2012/07/09/2583129.h ...

  4. 区块链+AI将给区块链带来怎样的改变?

    区块链和人工智能技术都是互联网时代最新.最热的技术,不仅可以改变我们生活,还能产生巨大的财富,为此国家大力支持发展,科技巨头们也纷纷布局.那区块链与人工智能结合,对区块链技术而言会产生什么样的化学反应 ...

  5. HLS切片机

    参考: 1,linux下搭建生成HLS所需的.ts和.m3u8文件http://www.cnblogs.com/mystory/archive/2013/04/07/3006200.html2,iPh ...

  6. zabbix 用户自定义监控参数添加

    1. item  key的添加 key可以带参数,该参数为一个数组列表,可以同时传递多个参数,key的格式如下 key -- [ parameters] -- 例如: vfs.fs.size[/] v ...

  7. 闪动的Label控件

    带闪动效果带控件,目前只有Label,以后会逐步增加,如果有好看带效果也欢迎您带加入. 如果可能,请在github中star,您的支持是我继续完善的动力,非常感谢. 测试环境:Xcode 5.0,iO ...

  8. zoj 2316 Matrix Multiplication 解题报告

    题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=2316 题目意思:有 N 个 点,M 条 边.需要构造一个N * ...

  9. BZOJ1566 【NOI2009】管道取珠

    题面 这是一道DP神题,直到我写下这句题解时也没有想明白…… 首先,这道题要我们求所有(不同输出序列的方案数)的平方和,于是我们当然就想到求所有不同输出序列的方案数……(大雾) .这道题一个巧妙的地方 ...

  10. vim的tab缩进及用空格设置

    编辑~/.vimrc文件,分别设置用空格而不是用tab,一个tab多少个空格,自动缩进多少宽度,显示行号. set expandtabset tabstop=4 set shiftwidth=4 se ...