来了解一下锁吧

  • 首先提问,为什么多线程是不安全的? 其实不是线程的安全,而是内存的安全。
    现在大部分操作系统都是多任务的,即有多个进程同时运行,每个进程的内存空间内都有一块公共区域,叫做堆!进程内的所有线程都可以访问。所以,在一个线程A读取堆中的一个数据时,还没完成所有操作就去休息了,若现在再有一线程B进来修改了数据,A再次进来发现与上次读到的数据不一致。所以说多线程是不安全。
    即堆内存空间在没有保护机制的情况下,对多线程来说是不安全的地方,因为你放进去的数据,可能被别的线程操作。
    如果一些数据只有某个线程会使用,其它线程不能操作也不需要操作,这些数据就可以放入线程的栈内存中。较为常见的就是局部变量。

1、悲观锁和乐观锁
乐观锁:乐观锁持乐观态度,就是假设我的数据不会被意外修改,如果修改了,就放弃,从头再来。
悲观锁:悲观锁持悲观态度,就是假设我的数据一定会被意外修改,那干脆直接加锁得了。

2、CAS(Compare And Swap 比较并且替换):乐观锁的一种。使用了CAS,线程在读取数据时不加锁,准备写回数据时再读取一次,发现与上次读取的数据不一致,则不会写回数据,会读取当前数据,重复过程。但是这样也会遇到问题:

  • CPU开销问题,如果出现一直循环
  • 出现ABA问题:线程A读取数据为5,线程B进来把数据改为了6,线程C又进来把数据改回5。线程A在写回数据时判断数据一致,但是在交易的场景下还是很重要的。
    解决办法:加个自增字段,每次修改,加1。或增加一个时间戳。

3、悲观锁
synchronized
只允许单个线程执行的程序范围。
可修饰对象、方法和代码块。

1、synchronized对对象加锁,对象在内存中一共有三块区域:
对象头(Header)、实例数据(Instance Data)和对齐填充(Padding).

  • 对象头:对象头中保存了锁标志位和指向 monitor 对象的起始地址,如下图所示,右侧就是对象对应的 Monitor 对象
    在这里插入图片描述
    Monitor 中Owner会指向持有 Monitor 对象的线程。队列EntryList存放进入的线程,WaitSet存放等待获取锁的线程。

2、 synchronized对方法加锁时,在字节码中是通过方法的 ACC_SYNCHRONIZED 标志来实现的。

//反编译
synchronized void test();
  descriptor: ()V
  flags: ACC_SYNCHRONIZED
  Code:
    stack=0, locals=1, args_size=1
       0: return
    LineNumberTable:
      line 7: 0
    LocalVariableTable:
      Start  Length  Slot  Name   Signature
          0       1     0  this   Ljvm/ClassCompile;

其他线程进来后看有没有这个标志位。
3、synchronized 应用在同步块上时,在字节码中是通过 monitorenter 和 monitorexit 实现的。
每个对象都会与一个monitor相关联,当某个monitor被拥有之后就会被锁住,当线程执行到monitorenter指令时,就会去尝试获得对应的monitor。
步骤如下:

  • 每个monitor维护着一个记录着拥有次数的计数器。未被拥有的monitor的该计数器为0,当一个线程获得monitor(执行monitorenter)后,该计数器自增变为1 。
  • 当同一个线程再次获得该monitor的时候,计数器再次自增;
  • 当不同线程想要获得该monitor的时候,就会被阻塞。
  • 当同一个线程释放 monitor(执行monitorexit指令)的时候,计数器再自减。
  • 当计数器为0的时候,monitor将被释放,其他线程便可以获得monitor。

现在的synchronized已不是当年的重量锁了,Java SE 1.6 对 synchronized 进行了各种优化。针对 synchronized 获取锁的方式,JVM 使用了锁升级的优化方式,就是先使用偏向锁优先同一线程然后再次获取锁,如果失败,就升级为 CAS 轻量级锁,如果失败就会短暂自旋,防止线程被系统挂起。最后如果以上都失败就升级为重量级锁。
在这里插入图片描述
ReentrantLock
ReentrantLock 就是基于 AQS 实现的。
先来说AQS(AbstractQueuedSynchronizer)
AQS:也就是队列同步器,这是实现 ReentrantLock 的基础。
AQS标记位state,值为1时表示有线程正在使用,新进来的线程进入同步队列中等待。同步队列是一个双向链表。
在这里插入图片描述
当获得锁的线程需要等待某个条件时,会进入 condition 的等待队列,等待队列可以有多个。
当 condition 条件满足时,线程会从等待队列重新进入同步队列进行获取锁的竞争。

ReentrantLock 就是基于 AQS 实现的,ReentrantLock 内部有公平锁和非公平锁两种实现,差别就在于新来的线程是否比已经在同步队列中的等待线程更早获得锁。
和 ReentrantLock 实现方式类似,Semaphore 也是基于 AQS 的,差别在于 ReentrantLock 是独占锁,Semaphore 是共享锁。
看原文

死锁
一个线程中有多个锁,多个线程同时执行,可能线程1获得A锁,线程2获得了B锁,然后线程A想得到B锁就一直等待有人释放,线程2想得到Asuo就等待两个人一直等待,两个线程各自持有一个,而且只有某个线程同时拥有AB锁才能执行,两个人都只得到一个,却谁都不愿意释放,导致两个线程都在等待。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值