互斥锁 Synchronized

2024-08-25 11:18
文章标签 互斥 synchronized

本文主要是介绍互斥锁 Synchronized,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、java语言提供的所技术:synchronized

       用synchronized 解决 count += 1的问题:

二、对象级别的锁和类级别的锁的区别

三、用一个锁来保护多个资源

四、死锁问题:


使用锁来保护资源,首先需要确定锁和被保护资源的关系。为被保护资源R创建一把锁LR,然后再操作被保护资源R的时候对R进行加锁和解锁处理。

一、java语言提供的所技术:synchronized

synchronized关键字,是锁的一种实现,可以用来修饰方法,代码块。为了确保lock() 和 unlock() 的成对出现。synchronized中内置了lock() 和 unlock() 方法。


对于修改代码块的时候,我们可以明确的看出,synchronized锁定的对象是obj这个对象,但在我们使用synchronized修饰方法(静态和非静态)的时候,并没有显示的指出锁定的是什么(也就是未指定被保护的资源)。
是因为java中存在一条隐式规则(该规则 与 静态和非静态 的 实现机制有关):
synchronized修饰的是静态方法:锁定的是当前类的class,也就是代码中的Class X;
synchronized修饰的是非静态方法:锁定的是当前的实例对象 this;

以上规则应该着重注意


用synchronized 解决 count += 1的问题:

在多核场景下,多线程共同处理count += 1时候,会由于缓存的可见性导致 count += 1 的结果与预期不符合(并发编程中缓存导致的可见性问题)
现在我们用synchronized来解决这个问题,我们需要确保同一个时刻只有一个线程进行 count += 1;所以用synchronized来修饰addOne()方法。

这种情况首先确保了同一个时刻只有一个线程执行addOne方法,确保了原子性,当前线程执行完成后,count的值根据happen-before中的管程中锁规则,对于后续进行加锁执行addOne方法的线程是可见的。无论由多少个线程来一共执行1000次addOne方法。count的值都会是预期的1000;

当我们想获取count的值的时候,我们需要借助get()方法。那么count的值对于get方法的可见性却无法保证。为了保证get方法的可见性,根据管程中锁的规则,需要是get和addOne使用同一把锁。这里面 addOne 使用的锁 是 this对象(java隐式规则),那么直接用synchronized修改get方法,从而get和addOne方法使用了同一把锁,从而确保了count对于两个方法的可见性。代码如下:

将以上代码转换为锁的模型大概是:


 

如果我们将addOne()方法活成static

这时候 根据 java的隐式规则 get 加锁的是 this对象,而 addOne 加锁的就是 Class x。这个时候两个方法的锁是不一样的。从而 管程中的锁的规则 就无法保证这两个方法间 对于共享变量 count的可见性了。

以上代码的锁的模型就是

 

综上:同一个资源不可以使用多个锁来进行锁定,但可以用一个锁来保护多个资源

二、对象级别的锁和类级别的锁的区别

  • 对象级别的锁 : 不同的对象之间并不共享同一把锁。在不同的线程下,通过不同的对象调用执行加锁方法,锁并不会起作用
  • 类级别的锁:所有的对象都共享同一把锁

 

三、用一个锁来保护多个资源

保护多个资源的时候,首先我们需要确定多个资源是否存在关联

保护没有关联的多个资源

多个资源没有关联关系,就例如银行账户的取款操作和账户的密码修改,密码和余额就是需要保护的没有关联的资源。那么不存在关联的不同的资源就使用不同的锁保护,各自管各自的。这样可以使不同的资源可以并行被使用,提升了性能。如果用通一把锁将没有关联的资源加锁,这些资源就只能串行使用用不同的锁对受保护的资源精细化管理。这种锁叫做 细粒度锁。


保护有关联的多个资源
提供一把可以覆盖所有受保护资源就可以。主要需要注意的是 synchronized 锁的是this对象还是Class。

例如:转账业务,从账户A中转出100元到账户B中,那么账户A和B就是存在关联的两个不同资源。

                 

为了保证transfer方法不存在并发问题。我们可以给他加上synchronized关键字修饰一下。那么可以看出来改锁 锁定的是 Account 的 this对象,那么对于 target 对象就保护不了。因而简单的加上synchronized并不能实现一把锁保护多个资源。

根据上面提到过的java隐式规则,如果transfer声明成静态方法。那么就可以达到,用一把Class的锁锁住了所有被保护的资源。但是需要考虑一下如果声明成静态方法,会比较浪费资源。参考 静态和非静态 方法的区别。
但我们可以通过代码块的方式 锁住 CLASS。

锁相当于保证了面向高级语言的原子性。

四、死锁问题:

在程序中通常使用细粒度锁来提高并发量,进行性能的提升,但是细粒度锁的代价是可能会造成死锁问题。死锁的定义:一组相互竞争资源的线程因互相等待,导致“永久”阻塞的现象。程序一旦发生了死锁,一般没有什么好办法,很多时候只能通过重启应用来解决。因此我们解决死锁问题的最好办法是 规避死锁。

想要规避死锁,那么必须知道产生死锁的条件。产生死锁的条件(同时发生以下场景):

  • 互斥:共享资源X和Y只能被一个线程占有
  • 占有且等待:线程T1已经取得了共享资源X,在等待线程Y的时候,不释放共享资源X。
  • 不可抢占:去ITA线程不可以强行获取线程T1获取到资源
  • 循环等待:线程T1等待线程T2的资源,线程T2等待线程T1的资源,就是循环等待

因此我们可以破坏其中某一个或者多个条件,则可以避免死锁的出现。其中,互斥条件是无法破坏掉的,因为锁的本质就是互斥,其他三个方法都是有办法破坏掉的。

  • 占有且等待:可以使得线程一次性申请 所有需要的资源,就不存在等待问题了。体现在代码中:将所有操作放在一个临界区,及同时用synchronized锁定所需要的资源。
  • 不可抢占:可以使得线程 在占有部分资源同时进一步申请资源时,如果为成功申请到资源,就将已有资源释放掉。体现在代码中:synchronized无法实现主动释放资源,如果申请不到资源,线程就会进入阻塞状态,阻塞状态的线程啥也做不了,因此无法在Java语言层面解决,不会是可以通过SDK层面解决的,通过Java并发包中提供的lock是可以解决这个问题的。
  • 循环等待:指定线程申请资源的顺序。体现在代码中:对不同的资源设定不同的属性id,对id进行排序。

在选择对那种条件进行破坏的时候,需要结合具体情况进行判断,破坏那种条件的成本最低。

参考文章-1
参考文章-2

这篇关于互斥锁 Synchronized的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1105410

相关文章

关键字synchronized、volatile的比较

关键字volatile是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好,并且volatile只能修饰于变量,而synchronized可以修饰方法,以及代码块。随着JDK新版本的发布,synchronized关键字的执行效率上得到很大提升,在开发中使用synchronized关键字的比率还是比较大的。多线程访问volatile不会发生阻塞,而synchronize

java线程深度解析(二)——线程互斥技术与线程间通信

http://blog.csdn.net/daybreak1209/article/details/51307679      在java多线程——线程同步问题中,对于多线程下程序启动时出现的线程安全问题的背景和初步解决方案已经有了详细的介绍。本文将再度深入解析对线程代码块和方法的同步控制和多线程间通信的实例。 一、再现多线程下安全问题 先看开启两条线程,分别按序打印字符串的

面试官:synchronized的锁升级过程是怎样的?

大家好,我是大明哥,一个专注「死磕 Java」系列创作的硬核程序员。 回答 在 JDK 1.6之前,synchronized 是一个重量级、效率比较低下的锁,但是在JDK 1.6后,JVM 为了提高锁的获取与释放效,,对 synchronized 进行了优化,引入了偏向锁和轻量级锁,至此,锁的状态有四种,级别由低到高依次为:无锁、偏向锁、轻量级锁、重量级锁。 锁升级就是无锁 —>

Java并发:互斥锁,读写锁,Condition,StampedLock

3,Lock与Condition 3.1,互斥锁 3.1.1,可重入锁 锁的可重入性(Reentrant Locking)是指在同一个线程中,已经获取锁的线程可以再次获取该锁而不会导致死锁。这种特性允许线程在持有锁的情况下,可以递归地调用自身的同步方法或代码块,而不会因为再次尝试获取相同的锁而被阻塞。显然,通常的锁都要设计成可重入的。否则就会发生死锁。 synchronized关键字,就是

【Java编程的思想】理解synchronized

用法和基本原理 synchronized可以用于修饰类的实例方法、静态方法和代码块 实例方法 在介绍并发基础知识的时候,有一部分是关于竞态条件的,当多个线程访问和操作同一个对象时,由于语句不是原子操作,所以得到了不正确的结果。这个地方就可以用synchronized进行处理 public class Counter {private int count;public synchroni

三个同步与互斥问题之生产者与消费者

#include<stdio.h> #include<pthread.h> pthread_mutex_t  mutex; #define Max 10 pthread_cond_t pro; pthread_cond_t con; int buffer=0;//全局变量----一开始为0,只有生产者可以执行 void deal_produce(

三个同步与互斥问题之哲学家就餐

#include<stdio.h> #include <semaphore.h> #include<pthread.h> //筷子作为mutex   pthread_mutex_t chopstick[5] ;   int eatnum[5]={5,5,5,5,5}; void *eat_think(void *arg)   {       int i= *(cha

FreeRTOS学习笔记—④RTOS通信管理篇/同步互斥与通信(正在更新中)

二、RTOS的核心功能   RTOS的核心功能块主要分为任务管理、内核管理、时间管理以及通信管理4部分,框架图如下所示:   (1)任务管理:负责管理和调度任务的执行,确保系统中的任务能够按照预期运行。   (2)内核管理:负责系统核心功能的管理,包括内存、中断、异常处理和系统启动等。   (3)时间管理:负责所有与时间相关的操作,包括系统时钟、定时器、任务延迟和周期性任务的执行。   (4)通

【大数据Java基础- Java并发 20】深入分析synchronized的实现原理

记得刚刚开始学习Java的时候,一遇到多线程情况就是synchronized,相对于当时的我们来说synchronized是这么的神奇而又强大,那个时候我们赋予它一个名字“同步”,也成为了我们解决多线程情况的百试不爽的良药。但是,随着我们学习的进行我们知道synchronized是一个重量级锁,相对于Lock,它会显得那么笨重,以至于我们认为它不是那么的高效而慢慢摒弃它。 诚然,随着Javs S

java线程 yield,sleep,join,synchronized wait notify notifyAll,ReentrantLock lock condition, 生产者消费者

yield,sleep,join yield,join,sleep,join是Thread中的方法,不需要 在synchronized 代码块中调用,和synchronized 没关系,也不会释放锁。 Thread.sleep(100);Thread.yield();Thread t;t.join(); (1)yield()不一定保证让出cpu yield()只是使当前线程重新回